<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://smiler488.com/blog</id>
    <title>Liangchao Deng · Research &amp; Engineering Notes</title>
    <updated>2026-01-06T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://smiler488.com/blog"/>
    <subtitle>Practical research notes on AI, plant phenotyping, imaging, and scientific software.</subtitle>
    <icon>https://smiler488.com/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[PhenoHUB: A Mobile Toolkit for Digital Plant Phenotyping]]></title>
        <id>https://smiler488.com/blog/phenohub-wechat-miniapp</id>
        <link href="https://smiler488.com/blog/phenohub-wechat-miniapp"/>
        <updated>2026-01-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A January 2026 snapshot of a WeChat Mini Program combining field utilities, weather queries, image tools, and experimental AI assistants.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="overview">Overview<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="PhenoHUB prototype screens and WeChat Mini Program code" src="https://smiler488.com/assets/images/phenohub-ebe92f3a9155f3ccabc80cad1940c845.png" width="771" height="366" class="img_ev3q"></p>
<p><strong>PhenoHUB</strong> is a WeChat Mini Program prototype that groups mobile utilities for plant-phenotyping work. The project explores how field measurements, weather queries, image analysis, and AI-assisted research tasks can be made easier to access from a phone.</p>
<p>This article records the <strong>January 2026 project snapshot</strong> represented by the interface above. It is not a live completeness report, and the presence of a module in the launcher does not by itself establish measurement accuracy or production readiness.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="prototype-modules">Prototype modules<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#prototype-modules" class="hash-link" aria-label="Direct link to Prototype modules" title="Direct link to Prototype modules" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="device-orientation-measurements">Device-orientation measurements<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#device-orientation-measurements" class="hash-link" aria-label="Direct link to Device-orientation measurements" title="Direct link to Device-orientation measurements" translate="no">​</a></h3>
<p>The leaf-angle utility uses phone motion sensors to display and record device orientation. It can support rapid relative measurements when the phone is aligned consistently with a leaf.</p>
<p>The result should not be described as a precise leaf angle without a defined mounting method, sensor calibration, reference plane, and validation against a trusted instrument. Device model, case geometry, operator alignment, and motion can all affect the reading.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="land-area-estimation">Land-area estimation<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#land-area-estimation" class="hash-link" aria-label="Direct link to Land-area estimation" title="Direct link to Land-area estimation" translate="no">​</a></h3>
<p>The area utility records a location track and estimates the enclosed polygon. It is useful for reconnaissance and rough field records, but consumer-phone positioning is not survey-grade.</p>
<ul>
<li class="">Accuracy varies with the device, satellite visibility, buildings, trees, and sampling interval.</li>
<li class="">Altitude from a phone location service is especially uncertain.</li>
<li class="">Boundaries used for contracts, regulation, engineering, or precision operations require suitable survey equipment.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="weather-and-environmental-context">Weather and environmental context<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#weather-and-environmental-context" class="hash-link" aria-label="Direct link to Weather and environmental context" title="Direct link to Weather and environmental context" translate="no">​</a></h3>
<p>The agricultural-weather module is designed to query location-based weather information. Values obtained from a weather service describe the provider's grid or station estimate; they are not direct measurements from the phone.</p>
<p>Soil moisture, canopy temperature, light intensity, or other local variables require an explicit data source or external sensor. The interface should always label the provider, observation or forecast time, units, and location.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="image-analysis">Image analysis<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#image-analysis" class="hash-link" aria-label="Direct link to Image analysis" title="Direct link to Image analysis" translate="no">​</a></h3>
<p>The image module provides an entry point for plant-image preprocessing and quantitative analysis. Any reported area, count, color, or shape trait depends on segmentation quality, scale calibration, and acquisition conditions.</p>
<p>Image color alone should not be presented as chlorophyll content without a documented calibration model and independent validation.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="experimental-ai-assistants">Experimental AI assistants<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#experimental-ai-assistants" class="hash-link" aria-label="Direct link to Experimental AI assistants" title="Direct link to Experimental AI assistants" translate="no">​</a></h3>
<p>The prototype includes entries for chart generation and research assistance. These tools can help explore CSV data or draft analytical ideas, but AI output must remain reviewable:</p>
<ul>
<li class="">Charts should be traced back to the exact input rows and transformations.</li>
<li class="">Statistical tests require their assumptions and sample structure to be checked.</li>
<li class="">Literature, journal, and writing suggestions can be incomplete or incorrect.</li>
<li class="">Research data sent to an external model are subject to that provider's privacy terms.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="architecture-snapshot">Architecture snapshot<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#architecture-snapshot" class="hash-link" aria-label="Direct link to Architecture snapshot" title="Direct link to Architecture snapshot" translate="no">​</a></h2>
<p>The mobile client uses the native WeChat Mini Program environment with reusable UI components and Canvas/ECharts-style visualization. Some analysis tasks can be local to the Mini Program, while network-dependent features can call an external API service.</p>
<p>This split keeps lightweight interaction on the phone but creates clear boundaries:</p>
<ol>
<li class="">Sensor and location permissions should be requested only when the user starts the relevant tool.</li>
<li class="">Local and remote processing must be identified in the interface.</li>
<li class="">Uploaded files and API requests need size limits, failure states, and privacy guidance.</li>
<li class="">Model-generated results should include the provider, model, and generation time when possible.</li>
</ol>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="appropriate-use">Appropriate use<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#appropriate-use" class="hash-link" aria-label="Direct link to Appropriate use" title="Direct link to Appropriate use" translate="no">​</a></h2>
<p>PhenoHUB is most suitable as a prototype for:</p>
<ul>
<li class="">Field notes and rapid exploratory measurements</li>
<li class="">Teaching mobile phenotyping concepts</li>
<li class="">Testing interaction designs before instrument integration</li>
<li class="">Providing one launch point for small research utilities</li>
</ul>
<p>It should not be treated as a replacement for calibrated scientific instruments, validated statistical software, or a laboratory data-management system.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="access-and-reproducibility">Access and reproducibility<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#access-and-reproducibility" class="hash-link" aria-label="Direct link to Access and reproducibility" title="Direct link to Access and reproducibility" translate="no">​</a></h2>
<p>The project repository is hosted on WeChat Git and currently requires authenticated access. External readers cannot reliably clone it from a public URL, so this article does not present public installation or contribution steps.</p>
<p>For an internal or authorized deployment, record at least:</p>
<ul>
<li class="">Mini Program revision and backend revision</li>
<li class="">Device model and operating-system version</li>
<li class="">Permission and calibration procedure</li>
<li class="">Weather, map, or AI provider and request time</li>
<li class="">Input files, parameters, raw readings, and exported results</li>
</ul>
<p>Access questions can be directed through the contact channels on the <a class="" href="https://smiler488.com/cv">CV page</a>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="development-priorities">Development priorities<a href="https://smiler488.com/blog/phenohub-wechat-miniapp#development-priorities" class="hash-link" aria-label="Direct link to Development priorities" title="Direct link to Development priorities" translate="no">​</a></h2>
<p>The next useful milestones are evidence-driven rather than percentage-based: validate each measurement against a reference method, label experimental modules clearly, add provenance to exports, document privacy boundaries, and test the complete workflow on both iOS and Android devices.</p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
        <category label="Artificial Intelligence" term="Artificial Intelligence"/>
        <category label="Data Analysis" term="Data Analysis"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Botanical Extract AI Pro: An Experimental Background-Isolation Workflow]]></title>
        <id>https://smiler488.com/blog/botanical-extract-ai-pro</id>
        <link href="https://smiler488.com/blog/botanical-extract-ai-pro"/>
        <updated>2025-12-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[An experimental web and batch workflow for placing plants on white backgrounds with multimodal image models, including scientific and privacy limitations.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="overview">Overview<a href="https://smiler488.com/blog/botanical-extract-ai-pro#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="Examples of source plant images and AI-generated white-background outputs" src="https://smiler488.com/assets/images/botanical-extract-ai-pro-87a301e9f485f7c0856e0504fccb9366.png" width="891" height="377" class="img_ev3q"></p>
<p><strong>Botanical Extract AI Pro</strong> explores whether a multimodal image model can reduce background clutter in plant photographs without project-specific model training. It provides an interactive web workflow for individual images and a Node.js-oriented workflow for repeated processing.</p>
<p>This is best described as <strong>AI-assisted background isolation</strong>, not validated scientific segmentation. A generative model may redraw leaves, remove thin structures, change colors, or invent boundaries even when the prompt asks it not to.</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Experimental output</div><div class="admonitionContent_BuS1"><p>“Zero-shot” means that no task-specific training was performed. It does not imply zero error, pixel-perfect preservation, or suitability for quantitative phenotyping.</p></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="workflow">Workflow<a href="https://smiler488.com/blog/botanical-extract-ai-pro#workflow" class="hash-link" aria-label="Direct link to Workflow" title="Direct link to Workflow" translate="no">​</a></h2>
<p>The two clients follow the same basic path:</p>
<ol>
<li class="">Load an image and decode it to confirm the format and dimensions.</li>
<li class="">Select the closest aspect ratio supported by the chosen model API.</li>
<li class="">Send the image and a constrained background-replacement instruction to the provider.</li>
<li class="">Save the returned image beside the untouched original.</li>
<li class="">Record the provider, model, prompt version, time, and processing outcome.</li>
</ol>
<p>The web interface supports visual comparison for a single sample. The batch workflow scans a directory, mirrors its structure in an output directory, and records successes and failures.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="what-the-prompt-can-and-cannot-do">What the prompt can and cannot do<a href="https://smiler488.com/blog/botanical-extract-ai-pro#what-the-prompt-can-and-cannot-do" class="hash-link" aria-label="Direct link to What the prompt can and cannot do" title="Direct link to What the prompt can and cannot do" translate="no">​</a></h2>
<p>A prompt can request a white background and ask the model to preserve the plant. It cannot force a generative system to retain the original pixels. Statements such as “pixel-perfect” are therefore <strong>instructions to the model</strong>, not guarantees about the result.</p>
<p>Aspect-ratio matching is similarly limited. Choosing the nearest supported ratio can reduce avoidable cropping or stretching in the API request, but it does not guarantee identical geometry or resolution in the generated image.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="input-validation">Input validation<a href="https://smiler488.com/blog/botanical-extract-ai-pro#input-validation" class="hash-link" aria-label="Direct link to Input validation" title="Direct link to Input validation" translate="no">​</a></h2>
<p>File extensions alone are not sufficient validation. A safer client should:</p>
<ul>
<li class="">Check the binary signature and successfully decode the image</li>
<li class="">Enforce file-size and pixel-dimension limits</li>
<li class="">Reject unsupported or malformed data</li>
<li class="">Normalize orientation without silently discarding the original</li>
<li class="">Keep provider payloads separate from local metadata</li>
</ul>
<p>The current Base64-style request pattern loads the image into memory. It should not be described as streaming, and large inputs need explicit limits.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="bounded-batch-processing">Bounded batch processing<a href="https://smiler488.com/blog/botanical-extract-ai-pro#bounded-batch-processing" class="hash-link" aria-label="Direct link to Bounded batch processing" title="Direct link to Bounded batch processing" translate="no">​</a></h2>
<p><code>Promise.allSettled(files.map(processFile))</code> does <strong>not</strong> control concurrency; it schedules every task immediately. A production batch client should place a small limit around the provider call:</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword module" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports">pLimit</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'p-limit'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> limit </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">pLimit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> results </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">await</span><span class="token plain"> </span><span class="token known-class-name class-name">Promise</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">allSettled</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  files</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">limit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">processOneImage</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">file</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>The processing function should also use capped exponential backoff for retryable <code>429</code> and <code>5xx</code> responses, stop retrying permanent errors, and write a resumable manifest. Concurrency must be adjusted to the provider's documented limits and the available memory.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="scientific-quality-control">Scientific quality control<a href="https://smiler488.com/blog/botanical-extract-ai-pro#scientific-quality-control" class="hash-link" aria-label="Direct link to Scientific quality control" title="Direct link to Scientific quality control" translate="no">​</a></h2>
<p>Generated outputs should not be used directly for leaf area, shape, disease, growth, or time-series measurements without validation. A practical review should include:</p>
<ol>
<li class="">Overlay the source and output at the same scale.</li>
<li class="">Inspect thin stems, leaf tips, holes, flowers, labels, and pot boundaries.</li>
<li class="">Check whether colors, shadows, or plant geometry changed.</li>
<li class="">Compare a manually annotated validation subset with appropriate mask and boundary metrics.</li>
<li class="">Reject or manually correct failed samples before quantitative analysis.</li>
</ol>
<p>For measurements that require a reproducible binary mask, a conventional segmentation model or a validated interactive segmentation tool is usually more appropriate than image generation.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="privacy-cost-and-reproducibility">Privacy, cost, and reproducibility<a href="https://smiler488.com/blog/botanical-extract-ai-pro#privacy-cost-and-reproducibility" class="hash-link" aria-label="Direct link to Privacy, cost, and reproducibility" title="Direct link to Privacy, cost, and reproducibility" translate="no">​</a></h2>
<p>Unless a local model is used, uploaded images leave the device and are processed by an external provider. Before processing research data:</p>
<ul>
<li class="">Review the provider's retention and training policies.</li>
<li class="">Remove sensitive labels, locations, and personal information.</li>
<li class="">Store API keys in server-side or environment configuration, never in public client code.</li>
<li class="">Estimate per-image cost and rate limits before starting a batch.</li>
<li class="">Preserve the original files and a machine-readable processing manifest.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="current-limitations">Current limitations<a href="https://smiler488.com/blog/botanical-extract-ai-pro#current-limitations" class="hash-link" aria-label="Direct link to Current limitations" title="Direct link to Current limitations" translate="no">​</a></h2>
<ul>
<li class="">No public benchmark currently establishes accuracy across species, organs, backgrounds, or imaging conditions.</li>
<li class="">Results may vary across model versions and repeated requests.</li>
<li class="">The workflow produces an edited image, not necessarily an alpha mask or class-labeled segmentation.</li>
<li class="">Large-scale processing requires bounded concurrency, recovery logs, and manual quality assurance.</li>
<li class="">The project should not be described as open source unless a public repository and license are provided.</li>
</ul>
<p>The project remains useful as a prototype for rapid visual cleanup and for studying how multimodal models behave on botanical imagery. Its outputs should be treated as generated derivatives, not as ground truth.</p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Artificial Intelligence" term="Artificial Intelligence"/>
        <category label="Computer Vision" term="Computer Vision"/>
        <category label="Image Analysis" term="Image Analysis"/>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[MCTP: A Multi-Modal Crop Phenotyping Workspace]]></title>
        <id>https://smiler488.com/blog/mctp-unified-phenotyping-platform</id>
        <link href="https://smiler488.com/blog/mctp-unified-phenotyping-platform"/>
        <updated>2025-11-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A desktop workspace for hyperspectral, LiDAR, RGB, and thermal crop-phenotyping workflows, with clear boundaries between shared UI and cross-modal fusion.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="overview">Overview<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="MCTP desktop launcher with four modality modules" src="https://smiler488.com/assets/images/mctp-a3e2c193dfe7f2e1f79851d669e02789.png" width="685" height="471" class="img_ev3q"></p>
<p><strong>MCTP (Multi-Modal Crop Phenotyping Platform)</strong> is a desktop data-processing workspace developed alongside a field phenotyping collaboration with Shufeng Bio. My contribution focused on system optimization and the data-processing and analysis workflows.</p>
<p>The interface brings hyperspectral, LiDAR, RGB, and thermal tools into one launcher. In this project snapshot, “unified” means a consistent entry point, interaction pattern, and export convention. It does <strong>not</strong> mean that the four modalities are automatically registered, fused, or interpreted by one model.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Project snapshot</div><div class="admonitionContent_BuS1"><p>This article describes the version represented by the available interface and module screenshots. Exact input formats and outputs should be confirmed in the build used for a specific experiment.</p></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="current-module-scope">Current module scope<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#current-module-scope" class="hash-link" aria-label="Direct link to Current module scope" title="Direct link to Current module scope" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="hyperspectral-processing">Hyperspectral processing<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#hyperspectral-processing" class="hash-link" aria-label="Direct link to Hyperspectral processing" title="Direct link to Hyperspectral processing" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="Hyperspectral module showing spectral-image analysis views" src="https://smiler488.com/assets/images/hyper-0ddc08d8ad83a43adae4d9d5f873a87d.png" width="689" height="458" class="img_ev3q"></p>
<p>The hyperspectral workflow is designed around paired ENVI files and wavelength metadata. Its project implementation includes:</p>
<ul>
<li class="">HDR/SPE ingestion and wavelength parsing</li>
<li class="">RGB quicklooks and vegetation-index views</li>
<li class="">Threshold-based plant masks and glare filtering</li>
<li class="">Mean-spectrum and summary exports in CSV/JSON form</li>
<li class="">Directory-level processing for compatible datasets</li>
</ul>
<p>Index values and masks depend on valid radiometric metadata and suitable thresholds. They should be inspected before downstream statistical analysis.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="lidar-processing">LiDAR processing<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#lidar-processing" class="hash-link" aria-label="Direct link to LiDAR processing" title="Direct link to LiDAR processing" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="LiDAR module for point-cloud loading and preprocessing" src="https://smiler488.com/assets/images/lidar1-598d07870bada0169d278c122bc51a54.png" width="688" height="418" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" alt="LiDAR segmentation and trait-tuning interface" src="https://smiler488.com/assets/images/lidar2-86def58667ed07be8849d16abd26f5c6.png" width="684" height="439" class="img_ev3q"></p>
<p>The LiDAR module groups common point-cloud preparation and trait-extraction operations:</p>
<ul>
<li class="">PLY, LAS, LAZ, and text-file input</li>
<li class="">Ground rebasing, voxel downsampling, cropping, and height coloring</li>
<li class="">DBSCAN-based clustering with interactive parameter tuning</li>
<li class="">Coverage, height percentiles, occupancy, and convex-hull summaries</li>
<li class="">Cropped point-cloud and JSON report export</li>
</ul>
<p>These outputs are algorithmic estimates. Ground selection, point density, occlusion, and clustering parameters can materially change the result.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="rgb-processing">RGB processing<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#rgb-processing" class="hash-link" aria-label="Direct link to RGB processing" title="Direct link to RGB processing" translate="no">​</a></h3>
<p>The RGB workflow combines color-index thresholding with morphology and connected-component operations. The project implementation uses ExG, CIVE, and VDI features, then applies cleanup and instance-labeling steps for group-level and per-plant summaries.</p>
<p>This classical computer-vision approach is transparent and tunable, but it is sensitive to illumination, reflections, labels, soil color, and overlapping plants. A representative subset should be checked before batch processing.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="thermal-processing">Thermal processing<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#thermal-processing" class="hash-link" aria-label="Direct link to Thermal processing" title="Direct link to Thermal processing" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="Thermal module with threshold controls and heatmap preview" src="https://smiler488.com/assets/images/thermal-c0b862fb6c4d2ec659997e4273d6b314.png" width="688" height="457" class="img_ev3q"></p>
<p>The thermal workflow pairs image and temperature-matrix inputs, then provides threshold and morphology controls with overlay, heatmap, and plant-only previews. Depending on the module build, outputs can include images, arrays, tables, and JSON summaries.</p>
<p>Temperature values are only meaningful when the camera export, orientation, calibration, and environmental assumptions are correct.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="recommended-workflow">Recommended workflow<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#recommended-workflow" class="hash-link" aria-label="Direct link to Recommended workflow" title="Direct link to Recommended workflow" translate="no">​</a></h2>
<ol>
<li class=""><strong>Archive raw data first.</strong> Keep the original files and acquisition metadata unchanged.</li>
<li class=""><strong>Open one representative sample.</strong> Confirm file pairing, orientation, units, and coordinate conventions.</li>
<li class=""><strong>Tune parameters visibly.</strong> Use previews to identify failure cases instead of relying on defaults.</li>
<li class=""><strong>Process a small batch.</strong> Check outputs against the raw data before scaling up.</li>
<li class=""><strong>Record the configuration.</strong> Save thresholds, voxel sizes, clustering settings, and software version with the results.</li>
</ol>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="boundaries-of-the-platform">Boundaries of the platform<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#boundaries-of-the-platform" class="hash-link" aria-label="Direct link to Boundaries of the platform" title="Direct link to Boundaries of the platform" translate="no">​</a></h2>
<p>The available project snapshot should be treated as a collection of modality-specific processing tools, not as an autonomous scientific interpretation system.</p>
<ul>
<li class="">Cross-modal registration and temporal analysis are not current automatic capabilities.</li>
<li class="">Batch behavior differs by module; interactive tuning remains important for LiDAR and thermal data.</li>
<li class="">Structured exports improve handoff to R, Python, or other statistics tools, but they do not replace quality control.</li>
<li class="">No cloud processing service is documented in this snapshot.</li>
<li class="">Reproducibility requires the raw data, module version, parameters, calibration information, and exported results to be stored together.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="next-development-priorities">Next development priorities<a href="https://smiler488.com/blog/mctp-unified-phenotyping-platform#next-development-priorities" class="hash-link" aria-label="Direct link to Next development priorities" title="Direct link to Next development priorities" translate="no">​</a></h2>
<p>Potential future work includes shared project configuration files, explicit provenance records, cross-modal registration, and validated temporal analysis. These are development directions rather than claims about the current release.</p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
        <category label="Image Analysis" term="Image Analysis"/>
        <category label="Data Analysis" term="Data Analysis"/>
        <category label="Remote Sensing" term="Remote Sensing"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Predicting Leaf BRDF from Phenotypic Traits]]></title>
        <id>https://smiler488.com/blog/brdf-paper</id>
        <link href="https://smiler488.com/blog/brdf-paper"/>
        <updated>2025-10-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A peer-reviewed framework combining directional spectroscopy, BRDF fitting, phenotypic traits, ensemble learning, and canopy ray tracing in four species.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="overview">Overview<a href="https://smiler488.com/blog/brdf-paper#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="Directional spectrum measurement and BRDF prediction workflow" src="https://smiler488.com/assets/images/brdf_cover-5aabff311ab2865f0245b78b6754f1ad.jpg" width="2236" height="1696" class="img_ev3q"></p>
<p>Leaf surfaces do not reflect light uniformly. Their anatomy, pigments, and microscopic roughness change how radiation is scattered through a canopy, yet many canopy models use simplified optical inputs.</p>
<p>This study combines a custom <strong>Directional Spectrum Detection Instrument (DSDI)</strong>, Cook–Torrance <strong>bidirectional reflectance distribution function (BRDF)</strong> fitting, phenotypic measurements, and ensemble learning. The goal is to estimate leaf optical parameters from traits that are easier to measure and then examine how those parameters affect simulated canopy light distribution.</p>
<div class="altmetric-embed brdfAltmetric" data-badge-type="donut" data-doi="10.1016/j.plaphe.2025.100135" data-hide-no-mentions="false"></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="at-a-glance">At a glance<a href="https://smiler488.com/blog/brdf-paper#at-a-glance" class="hash-link" aria-label="Direct link to At a glance" title="Direct link to At a glance" translate="no">​</a></h2>
<ul>
<li class=""><strong>Plant material:</strong> maize, rice, cotton, and poplar leaves from upper and lower canopy positions.</li>
<li class=""><strong>Directional spectra:</strong> 400–1000 nm, measured across a broad angular range with the DSDI.</li>
<li class=""><strong>BRDF parameters:</strong> roughness $\sigma(\lambda)$, diffuse reflection coefficient $k(\lambda)$, and refractive index $n(\lambda)$.</li>
<li class=""><strong>Predictive model:</strong> a stacking ensemble built from support vector, random forest, and gradient boosting regressors.</li>
<li class=""><strong>Reported performance:</strong> BRDF fitting $R^2 &gt; 0.95$; ensemble prediction $R^2 = 0.83$–$0.99$, depending on the parameter.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="measurement-and-modeling-workflow">Measurement and modeling workflow<a href="https://smiler488.com/blog/brdf-paper#measurement-and-modeling-workflow" class="hash-link" aria-label="Direct link to Measurement and modeling workflow" title="Direct link to Measurement and modeling workflow" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-measure-directional-reflectance">1. Measure directional reflectance<a href="https://smiler488.com/blog/brdf-paper#1-measure-directional-reflectance" class="hash-link" aria-label="Direct link to 1. Measure directional reflectance" title="Direct link to 1. Measure directional reflectance" translate="no">​</a></h3>
<p>The DSDI uses a xenon light source, a fiber spectrometer, and mechanically controlled illumination and viewing angles. A Lambertian white reference is used to calibrate reflectance before leaf measurements.</p>
<p>Both adaxial and abaxial leaf surfaces were measured. This matters because the two surfaces differ in epidermal structure and optical response.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-fit-the-brdf-model">2. Fit the BRDF model<a href="https://smiler488.com/blog/brdf-paper#2-fit-the-brdf-model" class="hash-link" aria-label="Direct link to 2. Fit the BRDF model" title="Direct link to 2. Fit the BRDF model" translate="no">​</a></h3>
<p>The Cook–Torrance formulation represents diffuse and specular reflection with three wavelength-dependent parameters:</p>
<table><thead><tr><th>Parameter</th><th>Physical interpretation</th><th>Related leaf properties</th></tr></thead><tbody><tr><td>$\sigma(\lambda)$</td><td>Microfacet roughness</td><td>Epidermal texture and surface irregularity</td></tr><tr><td>$k(\lambda)$</td><td>Diffuse reflection coefficient</td><td>Internal scattering and the diffuse contribution to reflectance</td></tr><tr><td>$n(\lambda)$</td><td>Refractive index</td><td>Refraction and interface reflection, influenced by tissue composition</td></tr></tbody></table>
<p>Adaptive grid search and least-squares optimization were used to fit these parameters to the measured directional spectra.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-predict-optical-parameters-from-traits">3. Predict optical parameters from traits<a href="https://smiler488.com/blog/brdf-paper#3-predict-optical-parameters-from-traits" class="hash-link" aria-label="Direct link to 3. Predict optical parameters from traits" title="Direct link to 3. Predict optical parameters from traits" translate="no">​</a></h3>
<p>The input variables included leaf thickness, specific leaf weight, pigment measurements, microscopy-derived surface roughness, and wavelength. The stacking model combines:</p>
<ul>
<li class="">Support Vector Regression (SVR)</li>
<li class="">Random Forest Regression (RFR)</li>
<li class="">Gradient Boosting Regression Trees (GBRT)</li>
<li class="">Linear regression as the meta-learner</li>
</ul>
<p>The resulting model provides a direct, data-driven link between measured phenotypic traits and BRDF parameters within the study domain.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-test-canopy-scale-consequences">4. Test canopy-scale consequences<a href="https://smiler488.com/blog/brdf-paper#4-test-canopy-scale-consequences" class="hash-link" aria-label="Direct link to 4. Test canopy-scale consequences" title="Direct link to 4. Test canopy-scale consequences" translate="no">​</a></h3>
<p>Predicted BRDF parameters were introduced into a rice-canopy ray-tracing workflow based on <strong>fastTracer</strong>. The simulations show that changing roughness, diffuse reflection, or refractive behavior can alter the vertical and angular distribution of light inside a canopy.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="what-the-results-support">What the results support<a href="https://smiler488.com/blog/brdf-paper#what-the-results-support" class="hash-link" aria-label="Direct link to What the results support" title="Direct link to What the results support" translate="no">​</a></h2>
<p>The study supports three practical conclusions:</p>
<ol>
<li class="">Directional leaf reflectance can be represented accurately with a physically based BRDF model.</li>
<li class="">Structural and biochemical leaf traits contain useful information for predicting BRDF parameters.</li>
<li class="">Leaf optical diversity can materially change simulated canopy light fields and should not always be treated as uniform.</li>
</ol>
<p>These results provide a route for connecting leaf-scale phenotyping to radiative-transfer and canopy-photosynthesis models.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="scope-and-limitations">Scope and limitations<a href="https://smiler488.com/blog/brdf-paper#scope-and-limitations" class="hash-link" aria-label="Direct link to Scope and limitations" title="Direct link to Scope and limitations" translate="no">​</a></h2>
<p>The model was developed from <strong>270 data entries</strong> spanning four species, two canopy positions, and both leaf surfaces. It is therefore a research model, not a universal estimator for every crop, genotype, environment, or stress treatment.</p>
<ul>
<li class="">Predictions outside the measured trait and wavelength ranges require new validation.</li>
<li class="">The ray-tracing results demonstrate changes in simulated light distribution; they do not by themselves demonstrate yield gains in the field.</li>
<li class="">Direct optical measurement remains important when working with new species or when high-accuracy optical parameters are required.</li>
<li class="">Future datasets should cover more genotypes, environments, developmental stages, and water-status conditions.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="code-and-data-availability">Code and data availability<a href="https://smiler488.com/blog/brdf-paper#code-and-data-availability" class="hash-link" aria-label="Direct link to Code and data availability" title="Direct link to Code and data availability" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://github.com/PlantSystemsBiology/brdf" target="_blank" rel="noopener noreferrer" class="">BRDF fitting scripts and Roughness Calculator</a></li>
<li class=""><a href="https://github.com/PlantSystemsBiology/fastTracerPublic" target="_blank" rel="noopener noreferrer" class="">fastTracer canopy ray-tracing software</a></li>
<li class="">The study data are available from the corresponding author upon reasonable request, as stated in the published article.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="citation">Citation<a href="https://smiler488.com/blog/brdf-paper#citation" class="hash-link" aria-label="Direct link to Citation" title="Direct link to Citation" translate="no">​</a></h2>
<p>Deng, L., Yu, L. X., Mao, L., Wang, Y., Guo, X., Wang, M., Zhang, Y., Song, Q., &amp; Zhu, X.-G. (2025). <strong>Leaf bidirectional reflectance distribution function (BRDF) prediction with phenotypic traits in four species: Development of a novel measuring and analyzing framework.</strong> <em>Plant Phenomics, 7</em>(4), 100135. <a href="https://doi.org/10.1016/j.plaphe.2025.100135" target="_blank" rel="noopener noreferrer" class="">https://doi.org/10.1016/j.plaphe.2025.100135</a></p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
        <category label="Remote Sensing" term="Remote Sensing"/>
        <category label="Machine Learning" term="Machine Learning"/>
        <category label="Crop Modeling" term="Crop Modeling"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Local AI Assistants and Agents: A Secure, Reproducible Deployment Guide]]></title>
        <id>https://smiler488.com/blog/local-ai-agent-deployment</id>
        <link href="https://smiler488.com/blog/local-ai-agent-deployment"/>
        <updated>2025-08-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A practical path from a local Ollama assistant to retrieval and controlled tool use, with explicit privacy, security, versioning, and deployment boundaries.]]></summary>
        <content type="html"><![CDATA[<p>Running a model locally can reduce dependence on a hosted inference API and keep prompts on controlled hardware. It does not automatically create an <strong>agent</strong>, and it does not guarantee privacy if the surrounding application uses remote search, telemetry, hosted embeddings, public tunnels, or networked tools.</p>
<p>This guide begins with a local assistant, then adds retrieval and optional tool use one boundary at a time. Each new capability should be observable, reversible, and no more privileged than the task requires.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="assistant-rag-system-or-agent">Assistant, RAG system, or agent?<a href="https://smiler488.com/blog/local-ai-agent-deployment#assistant-rag-system-or-agent" class="hash-link" aria-label="Direct link to Assistant, RAG system, or agent?" title="Direct link to Assistant, RAG system, or agent?" translate="no">​</a></h2>
<table><thead><tr><th>System</th><th>What it adds</th><th>Main risk</th></tr></thead><tbody><tr><td>Local assistant</td><td>A model that generates responses from prompts</td><td>Hallucination and accidental network exposure</td></tr><tr><td>Retrieval-augmented generation (RAG)</td><td>Search over a controlled document collection</td><td>Data leakage, stale indexes, and unsupported answers</td></tr><tr><td>Tool-using assistant</td><td>Structured calls to approved functions</td><td>Incorrect arguments and unintended side effects</td></tr><tr><td>Agent</td><td>A loop that chooses and executes multiple actions using state</td><td>Compounding errors, excessive autonomy, and unclear accountability</td></tr></tbody></table>
<p>An interactive chat with Ollama is a local assistant. Call it an agent only after adding an explicit action loop, tool contracts, state, stopping conditions, and authorization controls.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-write-the-deployment-boundary-first">1. Write the deployment boundary first<a href="https://smiler488.com/blog/local-ai-agent-deployment#1-write-the-deployment-boundary-first" class="hash-link" aria-label="Direct link to 1. Write the deployment boundary first" title="Direct link to 1. Write the deployment boundary first" translate="no">​</a></h2>
<p>Document these decisions before installation:</p>
<ul>
<li class="">Which data classifications may enter prompts?</li>
<li class="">Must the system work with the network disabled?</li>
<li class="">Which users and devices may access it?</li>
<li class="">Which tools are read-only, and which can change files or external systems?</li>
<li class="">Which actions require confirmation?</li>
<li class="">What is logged, for how long, and who can read it?</li>
<li class="">How will model, prompt, index, and tool versions be identified?</li>
<li class="">What is the rollback and incident-response path?</li>
</ul>
<p>“Local model” describes where inference runs. It does not answer these system-level questions.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-start-with-ollama-on-localhost">2. Start with Ollama on localhost<a href="https://smiler488.com/blog/local-ai-agent-deployment#2-start-with-ollama-on-localhost" class="hash-link" aria-label="Direct link to 2. Start with Ollama on localhost" title="Direct link to 2. Start with Ollama on localhost" translate="no">​</a></h2>
<p>Install Ollama from the <a href="https://ollama.com/download" target="_blank" rel="noopener noreferrer" class="">official download page</a>. Where the shell installer is supported:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -fsSL https://ollama.com/install.sh | sh</span><br></div></code></pre></div></div>
<p>Select a current model from the official library that fits the available memory and license requirements, then replace <code>&lt;model-name&gt;</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ollama pull &lt;model-name&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ollama run &lt;model-name&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ollama list</span><br></div></code></pre></div></div>
<p>Test the local chat API:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> requests</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">response </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> requests</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">post</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"http://localhost:11434/api/chat"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    json</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"model"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;model-name&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"messages"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"role"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"user"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"content"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Summarize this experiment plan in five bullets."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"stream"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    timeout</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">120</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">raise_for_status</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">response</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">json</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"message"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"content"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Install the only additional dependency used by this example:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python -m venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">source .venv/bin/activate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install requests</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip freeze &gt; requirements-lock.txt</span><br></div></code></pre></div></div>
<p>Keep the service on localhost while testing. Confirm listening interfaces with operating-system network tools before assuming it is private.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-record-the-environment-portably">3. Record the environment portably<a href="https://smiler488.com/blog/local-ai-agent-deployment#3-record-the-environment-portably" class="hash-link" aria-label="Direct link to 3. Record the environment portably" title="Direct link to 3. Record the environment portably" translate="no">​</a></h2>
<p>This Python report works across major desktop platforms without Linux-only commands such as <code>free</code> or <code>nproc</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> os</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> platform</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> shutil</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">report </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"platform"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> platform</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">platform</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"python"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> platform</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">python_version</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"cpu_count"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cpu_count</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"ollama_path"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> shutil</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">which</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"ollama"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"docker_path"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> shutil</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">which</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"docker"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">try</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> torch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"torch"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">__version__</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"cuda_available"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"gpu"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get_device_name</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">except</span><span class="token plain"> ImportError</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"torch"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">dumps</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">report</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> indent</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Memory requirements depend on model, quantization, context, parallel requests, and runtime overhead. Benchmark the chosen artifact on the actual machine rather than labeling all 7B or 13B models with one hardware threshold.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-add-retrieval-without-hiding-provenance">4. Add retrieval without hiding provenance<a href="https://smiler488.com/blog/local-ai-agent-deployment#4-add-retrieval-without-hiding-provenance" class="hash-link" aria-label="Direct link to 4. Add retrieval without hiding provenance" title="Direct link to 4. Add retrieval without hiding provenance" translate="no">​</a></h2>
<p>A maintainable local RAG pipeline has five explicit stages:</p>
<ol>
<li class=""><strong>Ingest:</strong> allowlisted files are parsed; unsupported or encrypted files fail visibly.</li>
<li class=""><strong>Chunk:</strong> document structure and stable identifiers are preserved.</li>
<li class=""><strong>Embed:</strong> the exact embedding model and revision are recorded.</li>
<li class=""><strong>Retrieve:</strong> candidate chunks include source, page or section, score, and index version.</li>
<li class=""><strong>Generate:</strong> the prompt instructs the model to answer from evidence and cite retrieved sources.</li>
</ol>
<p>Keep the original document identifier in every chunk. Evaluate retrieval separately from answer generation: if the relevant passage is not retrieved, changing the final prompt cannot repair the evidence gap.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="minimal-evaluation-set">Minimal evaluation set<a href="https://smiler488.com/blog/local-ai-agent-deployment#minimal-evaluation-set" class="hash-link" aria-label="Direct link to Minimal evaluation set" title="Direct link to Minimal evaluation set" translate="no">​</a></h3>
<p>Create questions with expected source passages and include:</p>
<ul>
<li class="">answerable questions;</li>
<li class="">questions whose answer is absent;</li>
<li class="">conflicting documents;</li>
<li class="">superseded versions;</li>
<li class="">tables, captions, and long sections;</li>
<li class="">adversarial text embedded inside a document.</li>
</ul>
<p>Measure retrieval recall, citation correctness, unsupported-claim rate, latency, and behavior when evidence is missing.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-add-tools-through-narrow-contracts">5. Add tools through narrow contracts<a href="https://smiler488.com/blog/local-ai-agent-deployment#5-add-tools-through-narrow-contracts" class="hash-link" aria-label="Direct link to 5. Add tools through narrow contracts" title="Direct link to 5. Add tools through narrow contracts" translate="no">​</a></h2>
<p>Do not give a model a general shell, unrestricted filesystem, or broad API token as the first tool. Wrap each capability in a small typed function.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> dataclasses </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> dataclass</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> pathlib </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Path</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:#393A34">@dataclass</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">frozen</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">ReadTextRequest</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    relative_path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">read_project_text</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">request</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ReadTextRequest</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> workspace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    root </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> workspace</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    target </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">root </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">relative_path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">resolve</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> root </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> target</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parents</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Path escapes the approved workspace"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> target</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">suffix</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">lower</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">".md"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".txt"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".csv"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"File type is not allowed"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> target</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">stat</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">st_size </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1_000_000</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"File exceeds the read limit"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> target</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_text</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">encoding</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"utf-8"</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>This example is read-only and workspace-scoped. A production tool still needs structured error handling, audit identifiers, denial tests, and protection against symbolic-link and race-condition edge cases.</p>
<p>For every tool, define:</p>
<ul>
<li class="">an input schema and size limits;</li>
<li class="">authentication context and least-privilege credentials;</li>
<li class="">allowed resources and denied paths;</li>
<li class="">timeout, retry, and idempotency behavior;</li>
<li class="">a preview for consequential actions;</li>
<li class="">user confirmation rules;</li>
<li class="">a structured, redacted audit event;</li>
<li class="">a deterministic stop condition.</li>
</ul>
<p>Treat retrieved text, webpages, emails, and documents as untrusted data. They can contain instructions designed to manipulate the model.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-control-the-agent-loop">6. Control the agent loop<a href="https://smiler488.com/blog/local-ai-agent-deployment#6-control-the-agent-loop" class="hash-link" aria-label="Direct link to 6. Control the agent loop" title="Direct link to 6. Control the agent loop" translate="no">​</a></h2>
<p>A safe loop should be bounded by design:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">receive request</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  → classify data and permissions</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  → propose a plan</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  → choose one allowlisted tool</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  → validate arguments</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  → request confirmation when required</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  → execute with timeout</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  → record a redacted result</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  → stop, or continue within a strict step budget</span><br></div></code></pre></div></div>
<p>Set limits for steps, wall time, tokens, tool calls, file volume, and retries. The model must not be able to increase its own limits or grant itself new tools.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-secure-the-service-before-sharing-it">7. Secure the service before sharing it<a href="https://smiler488.com/blog/local-ai-agent-deployment#7-secure-the-service-before-sharing-it" class="hash-link" aria-label="Direct link to 7. Secure the service before sharing it" title="Direct link to 7. Secure the service before sharing it" translate="no">​</a></h2>
<p>Minimum controls for any networked deployment include:</p>
<ul>
<li class="">bind only to the intended interface;</li>
<li class="">authenticate users and authorize each tool separately;</li>
<li class="">generate secrets outside source code and fail closed when missing;</li>
<li class="">use TLS across untrusted networks;</li>
<li class="">set request, context, concurrency, and rate limits;</li>
<li class="">keep document stores and vector databases off public ports;</li>
<li class="">redact prompts, documents, credentials, and personal data from logs;</li>
<li class="">separate model execution from privileged tools;</li>
<li class="">scan dependencies and pin container images by version or digest;</li>
<li class="">back up indexes and configuration with tested restore procedures;</li>
<li class="">provide a kill switch and revoke credentials after an incident.</li>
</ul>
<p>Character blacklists are not a defense against prompt injection. Likewise, a default JWT secret such as <code>your-secret-key</code> turns a configuration error into an authentication bypass.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="docker-and-gpu-notes">Docker and GPU notes<a href="https://smiler488.com/blog/local-ai-agent-deployment#docker-and-gpu-notes" class="hash-link" aria-label="Direct link to Docker and GPU notes" title="Direct link to Docker and GPU notes" translate="no">​</a></h2>
<p>A CPU Python base image does not gain CUDA support merely because a Compose file reserves a GPU. Use an inference image documented for the target runtime and driver, or keep Ollama outside the application container and call its restricted local endpoint.</p>
<p>Do not expose an unauthenticated vector database, model API, or development UI. Avoid <code>latest</code> image tags in a reproducible deployment. Follow current <a href="https://get.docker.com/" target="_blank" rel="noopener noreferrer" class="">Docker installation guidance</a> and NVIDIA container documentation rather than copying an old <code>apt-key</code> or <code>nvidia-docker2</code> setup script.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="operational-tests">Operational tests<a href="https://smiler488.com/blog/local-ai-agent-deployment#operational-tests" class="hash-link" aria-label="Direct link to Operational tests" title="Direct link to Operational tests" translate="no">​</a></h2>
<p>Before allowing real research data, test:</p>
<ul>
<li class="">service restart during a request;</li>
<li class="">malformed and oversized inputs;</li>
<li class="">prompt injection in retrieved documents;</li>
<li class="">tool arguments that escape the allowlist;</li>
<li class="">missing secrets and expired credentials;</li>
<li class="">unreachable model or vector store;</li>
<li class="">concurrent requests near the resource limit;</li>
<li class="">cancellation and timeout behavior;</li>
<li class="">restoration from backup;</li>
<li class="">model or index rollback.</li>
</ul>
<p>Record failure outcomes, not only successful demonstrations.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="reproducibility-manifest">Reproducibility manifest<a href="https://smiler488.com/blog/local-ai-agent-deployment#reproducibility-manifest" class="hash-link" aria-label="Direct link to Reproducibility manifest" title="Direct link to Reproducibility manifest" translate="no">​</a></h2>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"runtime"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Ollama"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"runtime_version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;version&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"model"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;model-name&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"model_digest"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;digest&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"prompt_version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"assistant-v1"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"embedding_model"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;model-and-revision&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"index_version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"documents-2026-07-16"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"tool_policy_version"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"read-only-v1"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"network_mode"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"localhost-only"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"evaluation_set"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"local-agent-eval-v1"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Store this record with evaluation results and deployment configuration.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="related-app-lab-tool">Related App Lab tool<a href="https://smiler488.com/blog/local-ai-agent-deployment#related-app-lab-tool" class="hash-link" aria-label="Direct link to Related App Lab tool" title="Direct link to Related App Lab tool" translate="no">​</a></h2>
<p><a class="" href="https://smiler488.com/app/solver">Multimodal AI Solver</a> is a browser client for user-supplied AI-provider credentials and multimodal requests. Its <a class="" href="https://smiler488.com/docs/tutorial-apps/ai-solver-tutorial">tutorial</a> describes provider, model, browser-permission, screen-capture, and privacy boundaries. It is not a local offline model runtime.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="final-checklist">Final checklist<a href="https://smiler488.com/blog/local-ai-agent-deployment#final-checklist" class="hash-link" aria-label="Direct link to Final checklist" title="Direct link to Final checklist" translate="no">​</a></h2>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Local assistant works before retrieval or tools are added</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Network behavior has been observed and documented</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Model and dependency versions are pinned</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Retrieval citations are evaluated</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Tools are narrow and least-privileged</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Consequential actions require confirmation</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Agent loop has hard budgets and a stop condition</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Secrets fail closed and never use a default value</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Logs are redacted and access-controlled</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Rollback and kill-switch procedures are tested</li>
</ul>
<p><em>Workflow reviewed: July 2026. Re-check Ollama, container-runtime, and framework documentation before deployment.</em></p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Artificial Intelligence" term="Artificial Intelligence"/>
        <category label="Local AI" term="Local AI"/>
        <category label="Reproducible Research" term="Reproducible Research"/>
        <category label="Python" term="Python"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Local Image Quantification in Python: An Experimental, Auditable Workflow]]></title>
        <id>https://smiler488.com/blog/local-image-quantification-tutorial</id>
        <link href="https://smiler488.com/blog/local-image-quantification-tutorial"/>
        <updated>2025-07-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A compact OpenCV workflow for segmenting isolated biological samples, exporting pixel and calibrated size descriptors, and documenting the validation required before scientific use.]]></summary>
        <content type="html"><![CDATA[<p>Simple thresholding and connected-component analysis can quantify isolated biological samples photographed on a uniform background. This is useful for prototypes, teaching, and controlled screening, but it is not a universal plant-phenotyping method.</p>
<p>The workflow below deliberately requires an explicit image scale. If scale calibration fails, it is safer to report pixels than to invent millimetres.</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Experimental workflow</div><div class="admonitionContent_BuS1"><p>The example has not been validated for every camera, crop, sample type, background, or lighting condition. Inspect every overlay and compare a representative set with independent manual measurements before using the output in a paper, breeding decision, or quality-control process.</p></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="appropriate-use">Appropriate use<a href="https://smiler488.com/blog/local-image-quantification-tutorial#appropriate-use" class="hash-link" aria-label="Direct link to Appropriate use" title="Direct link to Appropriate use" translate="no">​</a></h2>
<p>This workflow assumes:</p>
<ul>
<li class="">samples are physically separated;</li>
<li class="">the background is mostly uniform and visible in the image corners;</li>
<li class="">the camera is approximately perpendicular to the sample plane;</li>
<li class="">a known-length reference lies in the same plane as the samples;</li>
<li class="">lens distortion and perspective are negligible or corrected;</li>
<li class="">the intended traits can be approximated from a two-dimensional silhouette.</li>
</ul>
<p>It is not appropriate for tangled roots, dense canopies, overlapping leaves, uncontrolled field scenes, or organs whose three-dimensional curvature is central to the measurement.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="measurement-plan">Measurement plan<a href="https://smiler488.com/blog/local-image-quantification-tutorial#measurement-plan" class="hash-link" aria-label="Direct link to Measurement plan" title="Direct link to Measurement plan" translate="no">​</a></h2>
<p>Before writing code, define each output:</p>
<table><thead><tr><th>Output</th><th>Operational definition</th></tr></thead><tbody><tr><td>Area</td><td>Foreground pixels inside one connected component, divided by scale squared</td></tr><tr><td>Perimeter</td><td>Length of the extracted external contour</td></tr><tr><td>Length and width</td><td>Long and short sides of the minimum-area rotated rectangle</td></tr><tr><td>Aspect ratio</td><td>Rotated-rectangle length divided by width</td></tr><tr><td>Circularity</td><td><code>4π × area / perimeter²</code>; sensitive to contour noise</td></tr><tr><td>Mean RGB</td><td>Mean source-image channel value inside the component mask</td></tr></tbody></table>
<p>These are image descriptors. For example, minimum-rectangle length is not automatically equivalent to botanical leaf length along a curved midrib.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-capture-controlled-images">1. Capture controlled images<a href="https://smiler488.com/blog/local-image-quantification-tutorial#1-capture-controlled-images" class="hash-link" aria-label="Direct link to 1. Capture controlled images" title="Direct link to 1. Capture controlled images" translate="no">​</a></h2>
<ol>
<li class="">Use diffuse, stable illumination and a matte background with strong contrast.</li>
<li class="">Keep camera distance, focal length, exposure, white balance, and orientation fixed.</li>
<li class="">Place a calibrated reference in the sample plane, not above or below it.</li>
<li class="">Avoid touching or overlapping samples.</li>
<li class="">Preserve the original image and metadata.</li>
<li class="">Photograph an empty background and known-size validation objects during each session.</li>
</ol>
<p>If color is an outcome, use a color target and a controlled color-management workflow. Camera RGB values are device- and illumination-dependent.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-calculate-scale-explicitly">2. Calculate scale explicitly<a href="https://smiler488.com/blog/local-image-quantification-tutorial#2-calculate-scale-explicitly" class="hash-link" aria-label="Direct link to 2. Calculate scale explicitly" title="Direct link to 2. Calculate scale explicitly" translate="no">​</a></h2>
<p>Measure the visible reference span in pixels using a reviewed image or calibration tool:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pixels_per_mm = reference_length_pixels / reference_length_mm</span><br></div></code></pre></div></div>
<p>For example, a 25 mm marker spanning 310 pixels gives <code>12.4 px/mm</code>. Record how endpoints were selected. Do not fall back to an arbitrary constant when the marker is missing or ambiguous.</p>
<p>For higher-accuracy work, calibrate lens distortion and estimate a planar transform from multiple reference points rather than relying on one length.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-install-a-minimal-environment">3. Install a minimal environment<a href="https://smiler488.com/blog/local-image-quantification-tutorial#3-install-a-minimal-environment" class="hash-link" aria-label="Direct link to 3. Install a minimal environment" title="Direct link to 3. Install a minimal environment" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python -m venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">source .venv/bin/activate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install --upgrade pip</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install opencv-python numpy pandas</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip freeze &gt; requirements-lock.txt</span><br></div></code></pre></div></div>
<p>On Windows, activate with <code>.venv\Scripts\activate</code>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-run-a-compact-reference-implementation">4. Run a compact reference implementation<a href="https://smiler488.com/blog/local-image-quantification-tutorial#4-run-a-compact-reference-implementation" class="hash-link" aria-label="Direct link to 4. Run a compact reference implementation" title="Direct link to 4. Run a compact reference implementation" translate="no">​</a></h2>
<p>Save the following as <code>quantify_samples.py</code>. It estimates the background from the image corners, applies Otsu thresholding to color distance, filters small connected components, and exports both a CSV and an overlay.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> argparse</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> pathlib </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Path</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> cv2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> numpy </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> np</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> pandas </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> pd</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">foreground_mask</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">image_bgr</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ndarray</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ndarray</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    height</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> width </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> image_bgr</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">shape</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    border </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">max</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">min</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">height</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> width</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">//</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">20</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    lab </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cvtColor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">image_bgr</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">COLOR_BGR2LAB</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    corners </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        lab</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">border</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">border</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        lab</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">border</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain">border</span><span class="token punctuation" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        lab</span><span class="token punctuation" style="color:#393A34">[</span><span class="token operator" style="color:#393A34">-</span><span class="token plain">border</span><span class="token punctuation" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">border</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        lab</span><span class="token punctuation" style="color:#393A34">[</span><span class="token operator" style="color:#393A34">-</span><span class="token plain">border</span><span class="token punctuation" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain">border</span><span class="token punctuation" style="color:#393A34">:</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    corner_pixels </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">vstack</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">block</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">reshape</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> block </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> corners</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    background </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">median</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">corner_pixels</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> axis</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    distance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">linalg</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">norm</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">lab</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">astype</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">float32</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> background</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> axis</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    distance_8bit </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">normalize</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        distance</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">255</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">NORM_MINMAX</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">astype</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uint8</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    _</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> mask </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">threshold</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        distance_8bit</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">255</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">THRESH_BINARY </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">THRESH_OTSU</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    kernel </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">getStructuringElement</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">MORPH_ELLIPSE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    mask </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">morphologyEx</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">mask</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">MORPH_OPEN</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">morphologyEx</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">mask</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">MORPH_CLOSE</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">quantify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">image_path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> px_per_mm</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">float</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> min_area</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> px_per_mm </span><span class="token operator" style="color:#393A34">&lt;=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"--px-per-mm must be a measured positive value"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    image </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">imread</span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">image_path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> image </span><span class="token keyword" style="color:#00009f">is</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> FileNotFoundError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Could not read </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">image_path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    mask </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> foreground_mask</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    count</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> labels</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> stats</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _ </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">connectedComponentsWithStats</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">mask</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    overlay </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">copy</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    rows </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> label </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token builtin">range</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> count</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        area_px </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">stats</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">label</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">CC_STAT_AREA</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> area_px </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> min_area</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">continue</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        component </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">where</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">labels </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> label</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">255</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">astype</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">uint8</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        contours</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _ </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">findContours</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            component</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">RETR_EXTERNAL</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">CHAIN_APPROX_SIMPLE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> contours</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token keyword" style="color:#00009f">continue</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        contour </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">max</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">contours</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> key</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">contourArea</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        perimeter_px </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">float</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">arcLength</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">contour</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">_</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">side_a</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> side_b</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> angle </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">minAreaRect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">contour</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        length_px</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> width_px </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">max</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">side_a</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> side_b</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">min</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">side_a</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> side_b</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        mean_b</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> mean_g</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> mean_r</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _ </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mean</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> mask</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">component</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        sample_id </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:#e3116c">f"S</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation builtin">len</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">rows</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation operator" style="color:#393A34">+</span><span class="token string-interpolation interpolation"> </span><span class="token string-interpolation interpolation number" style="color:#36acaa">1</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        rows</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">append</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"id"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> sample_id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"area_px"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> area_px</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"perimeter_px"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">perimeter_px</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"length_mm"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">length_px </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> px_per_mm</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"width_mm"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">width_px </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> px_per_mm</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"area_mm2"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">area_px </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> px_per_mm</span><span class="token operator" style="color:#393A34">**</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"circularity"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token number" style="color:#36acaa">4</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pi </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> area_px </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token builtin">max</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">perimeter_px</span><span class="token operator" style="color:#393A34">**</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1e-9</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"angle_deg"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">float</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">angle</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"mean_r"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">mean_r</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"mean_g"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">mean_g</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token string" style="color:#e3116c">"mean_b"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">mean_b</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        box </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">intp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">boxPoints</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">minAreaRect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">contour</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">drawContours</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">overlay</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">contour</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">255</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">120</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">drawContours</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">overlay</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">box</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">210</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">255</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        x</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> y</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> _ </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">boundingRect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">contour</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">putText</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            overlay</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            sample_id</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">x</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">max</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">20</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> y </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">FONT_HERSHEY_SIMPLEX</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token number" style="color:#36acaa">0.6</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">20</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">20</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">20</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">LINE_AA</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    result </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">DataFrame</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rows</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    csv_path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> image_path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">with_name</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">image_path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">stem</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">_measurements.csv"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    overlay_path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> image_path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">with_name</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">image_path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">.</span><span class="token string-interpolation interpolation">stem</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">_overlay.png"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    result</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_csv</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">csv_path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">False</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    cv2</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">imwrite</span><span class="token punctuation" style="color:#393A34">(</span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">overlay_path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> overlay</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Detected </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation builtin">len</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">(</span><span class="token string-interpolation interpolation">result</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">)</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> components"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string-interpolation string" style="color:#e3116c">f"Wrote </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">csv_path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c"> and </span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">{</span><span class="token string-interpolation interpolation">overlay_path</span><span class="token string-interpolation interpolation punctuation" style="color:#393A34">}</span><span class="token string-interpolation string" style="color:#e3116c">"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> __name__ </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"__main__"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    parser </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> argparse</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ArgumentParser</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">add_argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"image"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">type</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">Path</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">add_argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"--px-per-mm"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">type</span><span class="token operator" style="color:#393A34">=</span><span class="token builtin">float</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> required</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">add_argument</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"--min-area"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">type</span><span class="token operator" style="color:#393A34">=</span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> default</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">500</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    args </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> parser</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parse_args</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    quantify</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">px_per_mm</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> args</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">min_area</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Run it with the measured scale:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python quantify_samples.py samples.jpg --px-per-mm 12.4 --min-area 500</span><br></div></code></pre></div></div>
<p>The script does not automatically identify or exclude the calibration object. Crop it out before analysis or remove its component only after checking the overlay and documenting the rule.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-review-before-accepting-numbers">5. Review before accepting numbers<a href="https://smiler488.com/blog/local-image-quantification-tutorial#5-review-before-accepting-numbers" class="hash-link" aria-label="Direct link to 5. Review before accepting numbers" title="Direct link to 5. Review before accepting numbers" translate="no">​</a></h2>
<p>For every batch:</p>
<ol>
<li class="">Compare the overlay with the original at full resolution.</li>
<li class="">Confirm the expected number of biological samples.</li>
<li class="">Reject components formed by shadows, labels, marker objects, or merged samples.</li>
<li class="">Repeat the scale measurement and report inter-operator variation.</li>
<li class="">Measure a blinded validation subset manually.</li>
<li class="">Report error and confidence intervals in physical units.</li>
<li class="">Test a second acquisition session before claiming generalization.</li>
</ol>
<p>Save exclusions rather than silently deleting them.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="interpretation-boundaries">Interpretation boundaries<a href="https://smiler488.com/blog/local-image-quantification-tutorial#interpretation-boundaries" class="hash-link" aria-label="Direct link to Interpretation boundaries" title="Direct link to Interpretation boundaries" translate="no">​</a></h2>
<ul>
<li class="">Downsampling or image compression can change contours and small structures.</li>
<li class="">Otsu thresholding assumes separable foreground and background distributions.</li>
<li class="">A rotated rectangle overestimates or misrepresents strongly curved samples.</li>
<li class="">Perimeter and circularity are highly sensitive to boundary noise.</li>
<li class="">Two-dimensional projected area is not total leaf or organ surface area.</li>
<li class="">Mean RGB is not a calibrated spectral measurement or a validated vegetation index.</li>
<li class="">The same settings may fail after changes in lighting, camera, background, crop, or growth stage.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="related-browser-tool">Related browser tool<a href="https://smiler488.com/blog/local-image-quantification-tutorial#related-browser-tool" class="hash-link" aria-label="Direct link to Related browser tool" title="Direct link to Related browser tool" translate="no">​</a></h2>
<p><a class="" href="https://smiler488.com/app/image">Biological Sample Quantifier</a> provides a browser entry point to a hosted image-analysis service. Review its <a class="" href="https://smiler488.com/docs/tutorial-apps/image-quantifier-tutorial">App Lab tutorial</a>, service status, upload limits, and external-processing privacy boundary before sending research images.</p>
<p><em>Reference implementation reviewed: July 2026. Validate it with your own acquisition protocol before scientific use.</em></p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Python" term="Python"/>
        <category label="Computer Vision" term="Computer Vision"/>
        <category label="Image Analysis" term="Image Analysis"/>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[From Manuscript to Publication: A Practical Guide for Researchers]]></title>
        <id>https://smiler488.com/blog/academic-paper-publication-guide</id>
        <link href="https://smiler488.com/blog/academic-paper-publication-guide"/>
        <updated>2025-06-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A concise, publisher-neutral guide to journal selection, manuscript preparation, submission, peer review, publication status, research integrity, and post-publication maintenance.]]></summary>
        <content type="html"><![CDATA[<p>Publishing a scientific paper is not a single submission event. It is a documented process that connects a research question, a defensible method, transparent evidence, appropriate journal selection, peer review, and long-term stewardship of the resulting paper, data, and code.</p>
<p>This guide provides a publisher-neutral workflow. Exact status labels, DOI timing, accepted-manuscript policies, and indexing practices vary by journal, so always follow the current instructions for the selected venue.</p>
<p><img decoding="async" loading="lazy" alt="Academic paper workflow illustration" src="https://smiler488.com/assets/images/write_paper-6e0950346b3aed4c44b6eb23053f6d74.png" width="1024" height="1024" class="img_ev3q"></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="publication-workflow">Publication workflow<a href="https://smiler488.com/blog/academic-paper-publication-guide#publication-workflow" class="hash-link" aria-label="Direct link to Publication workflow" title="Direct link to Publication workflow" translate="no">​</a></h2>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-define-the-contribution-before-choosing-a-journal">1. Define the contribution before choosing a journal<a href="https://smiler488.com/blog/academic-paper-publication-guide#1-define-the-contribution-before-choosing-a-journal" class="hash-link" aria-label="Direct link to 1. Define the contribution before choosing a journal" title="Direct link to 1. Define the contribution before choosing a journal" translate="no">​</a></h2>
<p>Write one sentence for each item:</p>
<ul>
<li class=""><strong>Problem:</strong> What unresolved question or practical limitation is addressed?</li>
<li class=""><strong>Evidence:</strong> What observations, experiments, simulations, or analyses answer it?</li>
<li class=""><strong>Contribution:</strong> What becomes possible or better understood because of the work?</li>
<li class=""><strong>Boundary:</strong> Where should the conclusion not be generalized?</li>
</ul>
<p>Choose the paper type that matches the evidence: research article, methods paper, data paper, software paper, brief report, perspective, or review. A new tool without validation is not automatically a methods contribution, and a large dataset without adequate documentation is not automatically reusable.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-select-a-journal-by-fit-and-trustworthiness">2. Select a journal by fit and trustworthiness<a href="https://smiler488.com/blog/academic-paper-publication-guide#2-select-a-journal-by-fit-and-trustworthiness" class="hash-link" aria-label="Direct link to 2. Select a journal by fit and trustworthiness" title="Direct link to 2. Select a journal by fit and trustworthiness" translate="no">​</a></h2>
<p>Start with scope and audience rather than a single metric.</p>
<table><thead><tr><th>Criterion</th><th>Questions to ask</th></tr></thead><tbody><tr><td>Scope</td><td>Has the journal recently published work with a similar question and evidence type?</td></tr><tr><td>Audience</td><td>Will the intended scientific community find and use the result?</td></tr><tr><td>Article format</td><td>Does it accept the manuscript, data, software, or methods format?</td></tr><tr><td>Review and production</td><td>Are typical timelines, editorial policies, and fees transparent?</td></tr><tr><td>Access and rights</td><td>What are the open-access options, licenses, and self-archiving rules?</td></tr><tr><td>Research integrity</td><td>Are ethics, corrections, retractions, and data policies clearly stated?</td></tr><tr><td>Indexing</td><td>Is the journal actually indexed in the databases relevant to the field?</td></tr></tbody></table>
<p>Impact Factor can describe a journal-level citation pattern; it does not measure the quality of an individual paper. Avoid journals that guarantee acceptance, imitate another journal's identity, conceal fees, or provide unverifiable editorial information.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-build-a-reproducible-manuscript-package">3. Build a reproducible manuscript package<a href="https://smiler488.com/blog/academic-paper-publication-guide#3-build-a-reproducible-manuscript-package" class="hash-link" aria-label="Direct link to 3. Build a reproducible manuscript package" title="Direct link to 3. Build a reproducible manuscript package" translate="no">​</a></h2>
<p>Most empirical papers use an IMRaD-like structure, but the journal's author guide takes precedence.</p>
<table><thead><tr><th>Section</th><th>Core job</th></tr></thead><tbody><tr><td>Introduction</td><td>Define the question, gap, and contribution without reviewing every related paper</td></tr><tr><td>Materials and Methods</td><td>Enable a qualified reader to understand and, where possible, reproduce the work</td></tr><tr><td>Results</td><td>Report evidence without hiding negative or null findings</td></tr><tr><td>Discussion</td><td>Interpret results, compare alternatives, and state limitations</td></tr><tr><td>Conclusion</td><td>Answer the research question without introducing new evidence</td></tr></tbody></table>
<p>Prepare the manuscript together with its supporting artifacts:</p>
<ul>
<li class="">figures and tables with units, sample sizes, uncertainty, and accessible labels;</li>
<li class="">data dictionary and analysis-ready data where sharing is permitted;</li>
<li class="">source code, environment information, and an executable workflow;</li>
<li class="">author contributions using a consistent taxonomy such as CRediT;</li>
<li class="">funding, conflicts of interest, ethics approvals, and consent statements;</li>
<li class="">data and code availability statements;</li>
<li class="">reporting checklist required by the field;</li>
<li class="">disclosure of any generative-AI use according to journal policy.</li>
</ul>
<p>Vector formats are useful for diagrams and plots when accepted, while raster images should meet the journal's dimensions, color mode, and resolution requirements. “300 dpi” alone is not a universal rule for every figure type.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-perform-a-pre-submission-audit">4. Perform a pre-submission audit<a href="https://smiler488.com/blog/academic-paper-publication-guide#4-perform-a-pre-submission-audit" class="hash-link" aria-label="Direct link to 4. Perform a pre-submission audit" title="Direct link to 4. Perform a pre-submission audit" translate="no">​</a></h2>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Title and abstract match the actual evidence.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Every stated objective is answered in the Results and Discussion.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Sample counts are consistent across text, tables, figures, and supplements.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Statistical units match the experimental design.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Code reproduces the final figures and tables from the archived inputs.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->References are complete and checked against primary sources.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->All authors approve the manuscript and author order.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Permissions are available for reused material.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->The manuscript is not simultaneously submitted elsewhere.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->The journal's formatting and policy checklist is complete.</li>
</ul>
<p>Reference managers such as Zotero or EndNote can reduce formatting work, but imported metadata still needs human verification.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-submit-a-complete-consistent-record">5. Submit a complete, consistent record<a href="https://smiler488.com/blog/academic-paper-publication-guide#5-submit-a-complete-consistent-record" class="hash-link" aria-label="Direct link to 5. Submit a complete, consistent record" title="Direct link to 5. Submit a complete, consistent record" translate="no">​</a></h2>
<p>Submission portals differ, but commonly request:</p>
<table><thead><tr><th>Item</th><th>Purpose</th></tr></thead><tbody><tr><td>Manuscript</td><td>Main scientific narrative</td></tr><tr><td>Figures and tables</td><td>Separate production-quality files when required</td></tr><tr><td>Supplementary material</td><td>Extended methods, results, media, or appendices</td></tr><tr><td>Cover letter</td><td>Journal fit, contribution, and required declarations</td></tr><tr><td>Author metadata</td><td>Names, affiliations, ORCID IDs, and contribution roles</td></tr><tr><td>Suggested or opposed reviewers</td><td>Expertise and conflicts, when requested</td></tr><tr><td>Data/code statement</td><td>Persistent links, access conditions, or justified restrictions</td></tr></tbody></table>
<p>Save the submitted PDF, source files, metadata, cover letter, and manuscript ID together. Check the generated submission PDF before final confirmation; conversion can alter equations, fonts, line breaks, and figure order.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-interpret-editorial-statuses-cautiously">6. Interpret editorial statuses cautiously<a href="https://smiler488.com/blog/academic-paper-publication-guide#6-interpret-editorial-statuses-cautiously" class="hash-link" aria-label="Direct link to 6. Interpret editorial statuses cautiously" title="Direct link to 6. Interpret editorial statuses cautiously" translate="no">​</a></h2>
<p>Status names are publisher-specific. The following table describes common patterns, not universal rules.</p>
<table><thead><tr><th>Typical status</th><th>Usual meaning</th><th>Public and citable?</th></tr></thead><tbody><tr><td>Submitted / With editor</td><td>Administrative or editorial assessment</td><td>Usually not public; a separate preprint may be citable</td></tr><tr><td>Under review</td><td>External review is in progress</td><td>Usually not public through the journal</td></tr><tr><td>Revision requested</td><td>Authors may submit a revised version and response</td><td>The decision is not acceptance</td></tr><tr><td>Accepted</td><td>Scientific decision is positive; production may not be complete</td><td>Citation format depends on journal and style; a DOI may not yet exist</td></tr><tr><td>Article in press / early view</td><td>A publisher-hosted version may be available before issue assignment</td><td>Often citable by DOI, but terminology varies</td></tr><tr><td>Version of record</td><td>Final publisher version</td><td>Citable using its final DOI and available bibliographic metadata</td></tr></tbody></table>
<p>Volume, issue, page range, article number, DOI assignment, online publication, and database indexing do not always occur at the same time. Verify the article record instead of inferring its state from one label.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-respond-to-reviewers-point-by-point">7. Respond to reviewers point by point<a href="https://smiler488.com/blog/academic-paper-publication-guide#7-respond-to-reviewers-point-by-point" class="hash-link" aria-label="Direct link to 7. Respond to reviewers point by point" title="Direct link to 7. Respond to reviewers point by point" translate="no">​</a></h2>
<p>A useful response document is easy to navigate and separates the reviewer's text, the response, and the exact manuscript change.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Reviewer 1, Comment 3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">[Paste the complete comment]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Response</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Thank you for identifying this ambiguity. We now define the biological</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">replicate before the statistical model and have rerun the analysis at the</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">plot level.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Change in manuscript</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Methods, Section 2.4: “The plot, rather than an individual image, was</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">treated as the biological replicate ...”</span><br></div></code></pre></div></div>
<p>When declining a suggestion, explain the scientific or practical reason and, when possible, add a limitation or alternative analysis. Do not claim a change was made if only the response letter changed.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="8-check-proofs-and-the-version-of-record">8. Check proofs and the version of record<a href="https://smiler488.com/blog/academic-paper-publication-guide#8-check-proofs-and-the-version-of-record" class="hash-link" aria-label="Direct link to 8. Check proofs and the version of record" title="Direct link to 8. Check proofs and the version of record" translate="no">​</a></h2>
<p>During production, verify:</p>
<ul>
<li class="">title, author names, affiliations, and corresponding-author details;</li>
<li class="">equations, symbols, units, and special characters;</li>
<li class="">figure resolution, labels, captions, and color interpretation;</li>
<li class="">table rows, footnotes, and supplementary links;</li>
<li class="">funding, ethics, data, and code statements;</li>
<li class="">references and DOI links.</li>
</ul>
<p>Proof correction is not normally a second opportunity to redesign the study. If a substantive error is discovered, contact the production editor transparently.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="9-cite-the-actual-publication-state">9. Cite the actual publication state<a href="https://smiler488.com/blog/academic-paper-publication-guide#9-cite-the-actual-publication-state" class="hash-link" aria-label="Direct link to 9. Cite the actual publication state" title="Direct link to 9. Cite the actual publication state" translate="no">​</a></h2>
<p>Use the metadata available for the version being cited and follow the required style. Do not invent a DOI, issue, or year for an accepted manuscript.</p>
<p>An updated APA-style reference for the example paper is:</p>
<blockquote>
<p>Deng, L., Yu, L. X., Mao, L., Wang, Y., Guo, X., Wang, M., Zhang, Y., Song, Q., &amp; Zhu, X.-G. (2025). Leaf bidirectional reflectance distribution function (BRDF) prediction with phenotypic traits in four species: Development of a novel measuring and analyzing framework. <em>Plant Phenomics, 7</em>(4), 100135. <a href="https://doi.org/10.1016/j.plaphe.2025.100135" target="_blank" rel="noopener noreferrer" class="">https://doi.org/10.1016/j.plaphe.2025.100135</a></p>
</blockquote>
<p>The DOI is the durable link: <a href="https://doi.org/10.1016/j.plaphe.2025.100135" target="_blank" rel="noopener noreferrer" class="">https://doi.org/10.1016/j.plaphe.2025.100135</a>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="10-maintain-the-research-record">10. Maintain the research record<a href="https://smiler488.com/blog/academic-paper-publication-guide#10-maintain-the-research-record" class="hash-link" aria-label="Direct link to 10. Maintain the research record" title="Direct link to 10. Maintain the research record" translate="no">​</a></h2>
<p>After publication:</p>
<ol>
<li class="">Deposit the permitted manuscript version according to the journal policy.</li>
<li class="">Update ORCID, institutional profiles, Google Scholar, Web of Science Researcher Profile, and the personal website.</li>
<li class="">Release data and code at the promised persistent locations.</li>
<li class="">Create a tagged software release matching the paper.</li>
<li class="">Monitor repository issues and document known limitations.</li>
<li class="">Correct material errors promptly through the appropriate journal mechanism.</li>
<li class="">Preserve the analysis environment and provenance needed to reproduce the figures.</li>
</ol>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="journal-analysis-workbook">Journal-analysis workbook<a href="https://smiler488.com/blog/academic-paper-publication-guide#journal-analysis-workbook" class="hash-link" aria-label="Direct link to Journal-analysis workbook" title="Direct link to Journal-analysis workbook" translate="no">​</a></h2>
<p>The accompanying workbook is a personal comparison aid, not an authoritative or permanently current ranking. Journal metrics, fees, scope, and review practices change; verify every decision on the official journal site.</p>
<p><a href="https://smiler488.com/assets/files/2025journalanalysis-c2fd38e83abedb34adc827e4753c8191.xlsx" target="_blank" class="">Download the journal-analysis workbook (.xlsx)</a></p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="final-checklist">Final checklist<a href="https://smiler488.com/blog/academic-paper-publication-guide#final-checklist" class="hash-link" aria-label="Direct link to Final checklist" title="Direct link to Final checklist" translate="no">​</a></h2>
<p>Define the contribution → select by fit → preserve provenance → write from evidence → submit consistently → respond transparently → verify the record → maintain artifacts.</p>
<p><strong>Citation of this guide</strong></p>
<p>Deng, L. (2025). <em>From manuscript to publication: A practical guide for researchers.</em> Digital Crop Photosynthesis Phenotyping Platform.</p>
<p><em>Content reviewed and bibliographic example updated: July 2026.</em></p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Scientific Writing" term="Scientific Writing"/>
        <category label="Reproducible Research" term="Reproducible Research"/>
        <category label="Productivity" term="Productivity"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Hunyuan3D-1 for Plant Images: A Reproducible Exploration Guide]]></title>
        <id>https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide</id>
        <link href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide"/>
        <updated>2025-02-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A version-specific, evidence-aware workflow for generating exploratory plant meshes with Hunyuan3D-1 and validating why generative 3D output is not automatically a phenotype measurement.]]></summary>
        <content type="html"><![CDATA[<p>Hunyuan3D-1 is a generative image-to-3D and text-to-3D system released by Tencent. It can create a textured 3D asset from a single plant photograph, which makes it useful for visualization, hypothesis generation, and studying the behavior of generative 3D models.</p>
<p>It is <strong>not</strong> a calibrated reconstruction system by default. A single image does not observe the back of a plant, an absolute scale, or structures hidden by leaves. The model must infer those regions, so its output cannot be treated as measured plant height, leaf area, branch count, or organ geometry without independent validation.</p>
<p><img decoding="async" loading="lazy" alt="Hunyuan3D plant reconstruction" src="https://smiler488.com/assets/images/i23d-8a74c4658cd7897237ac585be6ea7bac.png" width="758" height="493" class="img_ev3q"></p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Exploratory output, not ground truth</div><div class="admonitionContent_BuS1"><p>Use generated meshes as model outputs. Do not present inferred, invisible geometry as an observation. For metric phenotyping, compare against multi-view photogrammetry, depth sensing, laser scanning, or manual measurements collected with a documented scale.</p></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="scope-and-version">Scope and version<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#scope-and-version" class="hash-link" aria-label="Direct link to Scope and version" title="Direct link to Scope and version" translate="no">​</a></h2>
<p>This guide documents the public <strong>Hunyuan3D-1</strong> workflow so an older experiment can be reproduced. It deliberately does not invent a generic Transformers API: Hunyuan3D-1 uses its own repository, weight layout, environment scripts, and <code>main.py</code> entry point.</p>
<p>Check the upstream <a href="https://github.com/tencent/Hunyuan3D-1" target="_blank" rel="noopener noreferrer" class="">Hunyuan3D-1 repository</a> and <a href="https://huggingface.co/tencent/Hunyuan3D-1" target="_blank" rel="noopener noreferrer" class="">Hugging Face model card</a> before installation. Newer Hunyuan3D releases have different code and hardware requirements; do not mix commands between generations.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-decide-whether-the-method-fits-the-question">1. Decide whether the method fits the question<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#1-decide-whether-the-method-fits-the-question" class="hash-link" aria-label="Direct link to 1. Decide whether the method fits the question" title="Direct link to 1. Decide whether the method fits the question" translate="no">​</a></h2>
<table><thead><tr><th>Question</th><th>Hunyuan3D-1 suitability</th></tr></thead><tbody><tr><td>Can a generative model create a plausible plant-like asset?</td><td>Suitable for exploratory analysis</td></tr><tr><td>Can it support an interactive visualization or synthetic scene?</td><td>Potentially suitable after mesh inspection</td></tr><tr><td>What is the true height, leaf angle, or branch count of this plant?</td><td>Not suitable without calibrated reference data and validation</td></tr><tr><td>How does plant structure change over time?</td><td>A single-image generative output is not a reliable longitudinal measurement</td></tr><tr><td>Does the model generalize across species and growth stages?</td><td>Requires a pre-registered, independently labeled evaluation dataset</td></tr></tbody></table>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-prepare-an-auditable-input-set">2. Prepare an auditable input set<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#2-prepare-an-auditable-input-set" class="hash-link" aria-label="Direct link to 2. Prepare an auditable input set" title="Direct link to 2. Prepare an auditable input set" translate="no">​</a></h2>
<p>For each plant image, retain:</p>
<ul>
<li class="">the untouched original and metadata;</li>
<li class="">species or genotype and growth stage, if authorized for release;</li>
<li class="">camera and lighting information;</li>
<li class="">a background-removal method and its version;</li>
<li class="">an image identifier that links generated assets to source data;</li>
<li class="">consent and license information for any non-original images.</li>
</ul>
<p>Use a simple background with the complete visible plant in frame. Background removal may improve conditioning, but it does not reveal occluded organs or create metric scale.</p>
<p>Create a small pilot set first. Include easy examples, dense canopies, thin leaves, overlapping organs, and images from species not represented in the development set.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-install-the-official-hunyuan3d-1-workflow">3. Install the official Hunyuan3D-1 workflow<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#3-install-the-official-hunyuan3d-1-workflow" class="hash-link" aria-label="Direct link to 3. Install the official Hunyuan3D-1 workflow" title="Direct link to 3. Install the official Hunyuan3D-1 workflow" translate="no">​</a></h2>
<p>The commands below mirror the upstream project structure. They assume Linux with a compatible NVIDIA environment; choose the PyTorch build that matches the installed driver and CUDA runtime.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone https://github.com/tencent/Hunyuan3D-1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd Hunyuan3D-1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">conda create -n hunyuan3d-1 python=3.10</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">conda activate hunyuan3d-1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">which python</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">which pip</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Install the compatible PyTorch build first, following pytorch.org.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">bash env_install.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install "huggingface_hub[cli]"</span><br></div></code></pre></div></div>
<p>Download the official weights into the layout expected by the repository:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">mkdir -p weights</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">huggingface-cli download tencent/Hunyuan3D-1 --local-dir ./weights</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mkdir -p weights/hunyuanDiT</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">huggingface-cli download Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --local-dir ./weights/hunyuanDiT</span><br></div></code></pre></div></div>
<p>Record the Git commit and downloaded model revisions:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git rev-parse HEAD</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">huggingface-cli env</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip freeze &gt; environment-lock.txt</span><br></div></code></pre></div></div>
<p>The complete pipeline is GPU-intensive. Consult the upstream model card for the current standard, lite, split-pipeline, and memory-saving options. A successful Open3D installation or a random point-cloud visualization does not demonstrate that Hunyuan3D inference will run.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-generate-one-image-conditioned-asset">4. Generate one image-conditioned asset<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#4-generate-one-image-conditioned-asset" class="hash-link" aria-label="Direct link to 4. Generate one image-conditioned asset" title="Direct link to 4. Generate one image-conditioned asset" translate="no">​</a></h2>
<p>Run the repository entry point instead of calling nonexistent methods such as <code>AutoModel.generate_point_cloud</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python3 main.py \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --image_prompt "/absolute/path/to/plant.png" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --save_folder ./outputs/plant-001/ \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --max_faces_num 90000 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --do_texture_mapping \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --do_render</span><br></div></code></pre></div></div>
<p>Start with one image and inspect the entire output directory before attempting a batch. Save the command, random seed, runtime, peak GPU memory, warnings, and failure status for every sample.</p>
<p>The primary output is a generated mesh and render assets, not a measured point cloud. If a downstream application requires points, sample them from the mesh while preserving that provenance:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> open3d </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> o3d</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mesh </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> o3d</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">io</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_triangle_mesh</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"generated_mesh.obj"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> mesh</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_empty</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">raise</span><span class="token plain"> ValueError</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"The generated mesh could not be loaded"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mesh</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">compute_vertex_normals</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">points </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> mesh</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">sample_points_poisson_disk</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">number_of_points</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">100_000</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">o3d</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">io</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">write_point_cloud</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"generated_mesh_sampled.ply"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> points</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Replace <code>generated_mesh.obj</code> with the actual output filename. Sampling a mesh creates a point representation of the same generated surface; it does not make the geometry more accurate.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-evaluate-plant-outputs-honestly">5. Evaluate plant outputs honestly<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#5-evaluate-plant-outputs-honestly" class="hash-link" aria-label="Direct link to 5. Evaluate plant outputs honestly" title="Direct link to 5. Evaluate plant outputs honestly" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="visual-quality-review">Visual quality review<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#visual-quality-review" class="hash-link" aria-label="Direct link to Visual quality review" title="Direct link to Visual quality review" translate="no">​</a></h3>
<p>Score predefined criteria instead of selecting only attractive outputs:</p>
<ul>
<li class="">silhouette consistency with the visible image;</li>
<li class="">missing, duplicated, fused, or disconnected organs;</li>
<li class="">stem and branch continuity;</li>
<li class="">texture leakage and background artifacts;</li>
<li class="">plausibility of the inferred back side;</li>
<li class="">stability across seeds and small image perturbations.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="geometric-validation">Geometric validation<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#geometric-validation" class="hash-link" aria-label="Direct link to Geometric validation" title="Direct link to Geometric validation" translate="no">​</a></h3>
<p>If the research claim concerns geometry, collect an independently scaled reference for the same specimen. Align only with a documented procedure, then report:</p>
<ul>
<li class="">the number of successful and failed generations;</li>
<li class="">scale and registration method;</li>
<li class="">surface or point distance with units;</li>
<li class="">trait error for each biological unit;</li>
<li class="">performance by species, growth stage, and occlusion level;</li>
<li class="">sensitivity to seed, crop, background, and input view;</li>
<li class="">confidence intervals or repeated-run variability.</li>
</ul>
<p>Do not report benchmark numbers unless the dataset, split, baseline, code, and evaluation definition are available.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-reproducibility-record">6. Reproducibility record<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#6-reproducibility-record" class="hash-link" aria-label="Direct link to 6. Reproducibility record" title="Direct link to 6. Reproducibility record" translate="no">​</a></h2>
<p>Save a manifest such as:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"sample_id"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"plant-001"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"source_image"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"plant-001.png"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"repository_commit"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;git-commit&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"model_revision"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;model-revision&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"seed"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"command"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"python3 main.py ..."</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"status"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"success"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"intended_use"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"exploratory visualization"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"metric_validation"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Store failed generations too. Excluding them after inspection produces a misleading estimate of robustness.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="known-limitations">Known limitations<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#known-limitations" class="hash-link" aria-label="Direct link to Known limitations" title="Direct link to Known limitations" translate="no">​</a></h2>
<ul>
<li class="">Invisible and occluded geometry is inferred rather than observed.</li>
<li class="">Generated assets do not contain an automatic physical scale.</li>
<li class="">Thin leaves, petioles, branching junctions, and repeated textures are difficult cases.</li>
<li class="">A visually plausible mesh can still be geometrically wrong.</li>
<li class="">Output quality may vary with segmentation, background, seed, and input view.</li>
<li class="">Training-data coverage and species-specific generalization are not established by a few examples.</li>
<li class="">Model and code licenses must be checked for the intended research or commercial use.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="when-to-choose-another-method">When to choose another method<a href="https://smiler488.com/blog/hunyuan3d-plant-reconstruction-guide#when-to-choose-another-method" class="hash-link" aria-label="Direct link to When to choose another method" title="Direct link to When to choose another method" translate="no">​</a></h2>
<p>Use multi-view SfM for an image-based geometric reconstruction with camera calibration and scale. Use structured light, depth cameras, or laser scanning when reference geometry and repeatable measurements are the priority. Use Hunyuan3D when the research question is specifically about generative 3D behavior or when a clearly labeled synthetic visualization is appropriate.</p>
<p><em>Workflow reviewed: July 2026 for Hunyuan3D-1 documentation. Upstream commands and model availability may change.</em></p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Artificial Intelligence" term="Artificial Intelligence"/>
        <category label="Computer Vision" term="Computer Vision"/>
        <category label="3D Reconstruction" term="3D Reconstruction"/>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[UAV 3D Crop Phenotyping: From CCO Acquisition to Validated Traits]]></title>
        <id>https://smiler488.com/blog/uav-3d-crop-phenotyping</id>
        <link href="https://smiler488.com/blog/uav-3d-crop-phenotyping"/>
        <updated>2024-07-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A field-to-analysis workflow for Cross-Circular Oblique UAV acquisition, SfM reconstruction, spatial referencing, point-cloud phenotyping, and honest model validation.]]></summary>
        <content type="html"><![CDATA[<p>UAV-based 3D phenotyping connects flight planning, photogrammetry, point-cloud processing, and biological validation. The reconstruction is only one part of the workflow: flight safety, scale, coordinate reference, canopy motion, occlusion, and ground truth all determine whether a derived trait is useful.</p>
<p>This article presents a practical workflow built around <strong>Cross-Circular Oblique (CCO)</strong> acquisition and Structure from Motion (SfM). It distinguishes geometric measurements from visualization products and avoids treating every 3D representation as a calibrated phenotype.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="workflow-at-a-glance">Workflow at a glance<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#workflow-at-a-glance" class="hash-link" aria-label="Direct link to Workflow at a glance" title="Direct link to Workflow at a glance" translate="no">​</a></h2>
<!-- -->
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-design-acquisition-around-the-trait">1. Design acquisition around the trait<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#1-design-acquisition-around-the-trait" class="hash-link" aria-label="Direct link to 1. Design acquisition around the trait" title="Direct link to 1. Design acquisition around the trait" translate="no">​</a></h2>
<p>A <strong>Cross-Circular Oblique</strong> flight path collects oblique views around sampling locations or plots. The changing azimuth improves view diversity around complex canopies, while an additional grid or cross-hatch flight can provide more uniform coverage and strengthen the spatial frame.</p>
<p><img decoding="async" loading="lazy" alt="Flight Path Design Diagram" src="https://smiler488.com/assets/images/cco-a3f05a0205f19eef2bf776ea0594921f.png" width="416" height="244" class="img_ev3q"></p>
<p>Before defining waypoints, specify:</p>
<ul>
<li class="">the biological unit: plant, row, plot, or field;</li>
<li class="">the smallest structure that must remain visible;</li>
<li class="">the required ground sampling distance;</li>
<li class="">the expected canopy height and safe clearance;</li>
<li class="">camera focal length, focus, shutter speed, and image interval;</li>
<li class="">overlap targets appropriate to the scene and software;</li>
<li class="">wind, illumination, battery, and legal operating limits;</li>
<li class="">ground control points (GCPs), RTK/PPK strategy, or another source of scale.</li>
</ul>
<p>There is no universal altitude, radius, camera pitch, or overlap setting. Validate a small pilot block before committing to a field campaign.</p>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Flight safety and compatibility</div><div class="admonitionContent_BuS1"><p>Generated waylines are planning aids. Confirm local aviation rules, obstacle clearance, return-to-home behavior, battery reserve, aircraft and controller compatibility, and the imported mission in the manufacturer's software before flight.</p></div></div>
<p>The browser-based <a class="" href="https://smiler488.com/app/cco">CCO Waylines Builder</a> can turn a KML boundary into previewable DJI-compatible KML, WPML, and KMZ planning files. Its <a class="" href="https://smiler488.com/docs/tutorial-apps/cco-mission-planner-tutorial">tutorial</a> explains the current export and compatibility boundaries.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-control-image-quality-in-the-field">2. Control image quality in the field<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#2-control-image-quality-in-the-field" class="hash-link" aria-label="Direct link to 2. Control image quality in the field" title="Direct link to 2. Control image quality in the field" translate="no">​</a></h2>
<p>Photogrammetry cannot recover detail that is consistently blurred, saturated, occluded, or absent.</p>
<ol>
<li class="">Use manual or locked exposure where practical so brightness does not jump between adjacent frames.</li>
<li class="">Prefer a shutter speed that limits motion blur from both aircraft motion and wind-driven leaves.</li>
<li class="">Verify focus before each flight block.</li>
<li class="">Avoid abrupt illumination changes when possible; record cloud and wind conditions.</li>
<li class="">Check representative images at full resolution before leaving the site.</li>
<li class="">Preserve original files and metadata. Do not resize, sharpen, or re-encode the only copy.</li>
</ol>
<p>Record the aircraft, payload, lens, image dimensions, mission geometry, timestamps, coordinate system, weather, and any interrupted flight.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-build-the-spatial-reference">3. Build the spatial reference<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#3-build-the-spatial-reference" class="hash-link" aria-label="Direct link to 3. Build the spatial reference" title="Direct link to 3. Build the spatial reference" translate="no">​</a></h2>
<p>Multi-view images can be aligned and orthorectified to create an orthomosaic, digital surface model (DSM), camera poses, and dense point cloud. These products serve different purposes:</p>
<table><thead><tr><th>Product</th><th>Primary role</th><th>Important caveat</th></tr></thead><tbody><tr><td>Orthomosaic</td><td>Plot boundaries, labels, two-dimensional context, and spatial indexing</td><td>Relief displacement and canopy motion can affect seams</td></tr><tr><td>DSM</td><td>Surface elevation relative to the chosen reference</td><td>Height requires a defensible ground or terrain estimate</td></tr><tr><td>SfM point cloud</td><td>Geometric analysis and spatial sampling</td><td>Scale, noise, occlusion, and registration error must be checked</td></tr><tr><td>Camera poses</td><td>Reprojection, view selection, and downstream neural rendering</td><td>Pose quality depends on successful alignment</td></tr></tbody></table>
<p>Orthomosaics are not merely decorative baselines: they can support plot extraction and two-dimensional traits. They should not, however, be treated as the sole source of three-dimensional canopy structure.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-reconstruct-measurable-geometry-with-sfm">4. Reconstruct measurable geometry with SfM<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#4-reconstruct-measurable-geometry-with-sfm" class="hash-link" aria-label="Direct link to 4. Reconstruct measurable geometry with SfM" title="Direct link to 4. Reconstruct measurable geometry with SfM" translate="no">​</a></h2>
<p>SfM estimates camera poses and sparse geometry from shared visual features; dense reconstruction then estimates additional surface points.</p>
<p><img decoding="async" loading="lazy" alt="SfM Point Cloud Reconstruction Diagram" src="https://smiler488.com/assets/images/f3dr-8d3d33f8d6290b36d2eb7078cf427fc6.png" width="868" height="487" class="img_ev3q"></p>
<p>Before extracting traits, inspect:</p>
<ul>
<li class="">aligned-camera coverage and rejected images;</li>
<li class="">reprojection error and tie-point distribution;</li>
<li class="">GCP/RTK residuals and independent checkpoints;</li>
<li class="">scale and coordinate-system consistency;</li>
<li class="">holes beneath dense upper foliage;</li>
<li class="">floating points, ground leakage, and duplicated surfaces;</li>
<li class="">registration consistency across dates.</li>
</ul>
<p>Canopy height is typically calculated relative to a ground model or local reference surface, not directly from maximum point elevation. Report the percentile or robust statistic used; a single highest point is sensitive to noise.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-use-3d-gaussian-splatting-for-the-right-purpose">5. Use 3D Gaussian Splatting for the right purpose<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#5-use-3d-gaussian-splatting-for-the-right-purpose" class="hash-link" aria-label="Direct link to 5. Use 3D Gaussian Splatting for the right purpose" title="Direct link to 5. Use 3D Gaussian Splatting for the right purpose" translate="no">​</a></h2>
<p>3D Gaussian Splatting (3DGS) can provide high-quality interactive novel-view rendering from calibrated images and camera poses. It is valuable for visual inspection, communication, and research on appearance-aware scene representation.</p>
<p>3DGS should not automatically replace the SfM point cloud for metric phenotyping. A rendered Gaussian representation is not inherently a watertight surface or a calibrated biological measurement. Any geometry extracted from a 3DGS pipeline requires its own scale, accuracy, repeatability, and ground-truth validation.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-segment-fields-plots-and-rows">6. Segment fields, plots, and rows<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#6-segment-fields-plots-and-rows" class="hash-link" aria-label="Direct link to 6. Segment fields, plots, and rows" title="Direct link to 6. Segment fields, plots, and rows" translate="no">​</a></h2>
<p>Use the orthomosaic and experimental design to create plot polygons, then project or crop the corresponding point-cloud regions. A robust boundary workflow may combine:</p>
<ol>
<li class="">surveyed or design-derived plot geometry;</li>
<li class="">vegetation or color cues from the orthomosaic;</li>
<li class="">topology constraints such as expected row order and spacing;</li>
<li class="">manual quality control for borders, missing stands, and mixed plots.</li>
</ol>
<p>The <a class="" href="https://smiler488.com/app/land-survey">Land Surveyor</a> can help capture and export field polygons, but browser GPS accuracy varies by device and environment. Survey-grade boundaries require appropriate positioning equipment and procedures.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-derive-candidate-traits">7. Derive candidate traits<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#7-derive-candidate-traits" class="hash-link" aria-label="Direct link to 7. Derive candidate traits" title="Direct link to 7. Derive candidate traits" translate="no">​</a></h2>
<p>Candidate point-cloud descriptors can include:</p>
<ul>
<li class="">height percentiles and vertical profiles;</li>
<li class="">projected canopy area;</li>
<li class="">convex or alpha-shape volume with fully reported parameters;</li>
<li class="">point density and spatial occupancy;</li>
<li class="">row width and gap statistics;</li>
<li class="">surface roughness or local height variation.</li>
</ul>
<p>These are algorithm outputs until they are linked to a biological definition and validated. Point density, for example, also reflects image geometry, texture, reconstruction settings, and occlusion.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="canopypc-research-workflow">CanopyPC research workflow<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#canopypc-research-workflow" class="hash-link" aria-label="Direct link to CanopyPC research workflow" title="Direct link to CanopyPC research workflow" translate="no">​</a></h3>
<p>CanopyPC refers here to a research workflow for canopy point-cloud processing, including statistical outlier removal, optional ground filtering, row segmentation, bounding geometry, and interactive inspection. It should not be interpreted as a publicly released, installable package unless a repository, version, license, and example dataset are provided with the analysis.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="8-validate-before-modeling">8. Validate before modeling<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#8-validate-before-modeling" class="hash-link" aria-label="Direct link to 8. Validate before modeling" title="Direct link to 8. Validate before modeling" translate="no">​</a></h2>
<p>Select ground truth that matches the proposed claim. Examples include surveyed canopy height, manual organ measurements, destructive leaf-area measurements, or a higher-accuracy reference scan.</p>
<p>Use grouped validation that keeps related plants, plots, flights, sites, or dates together. Randomly splitting individual points or repeated images can create severe leakage.</p>
<p>Report at least:</p>
<ul>
<li class="">sample counts at the biological-unit level;</li>
<li class="">train, validation, and test grouping;</li>
<li class="">error metrics with units and uncertainty;</li>
<li class="">calibration and residual plots when predicting continuous traits;</li>
<li class="">performance by crop, growth stage, site, and acquisition condition;</li>
<li class="">missing reconstructions and excluded cases;</li>
<li class="">a comparison with a simple baseline.</li>
</ul>
<p>Transfer to a new crop or environment must be measured; it should not be assumed from cross-validation in one field.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="limitations-to-state-explicitly">Limitations to state explicitly<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#limitations-to-state-explicitly" class="hash-link" aria-label="Direct link to Limitations to state explicitly" title="Direct link to Limitations to state explicitly" translate="no">​</a></h2>
<ul>
<li class="">Dense upper canopies hide lower organs, and no flight path can reconstruct surfaces that are never observed.</li>
<li class="">Wind violates the static-scene assumption of conventional photogrammetry.</li>
<li class="">Repeated-date comparisons require stable scale, ground reference, and registration.</li>
<li class="">3D models may underestimate leaf area as occlusion increases.</li>
<li class="">Automatic segmentation can fail at plot borders, lodging, senescence, weeds, and missing stands.</li>
<li class="">Flight efficiency and geometric completeness trade off against battery, data volume, blur, and safety.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="reproducibility-checklist">Reproducibility checklist<a href="https://smiler488.com/blog/uav-3d-crop-phenotyping#reproducibility-checklist" class="hash-link" aria-label="Direct link to Reproducibility checklist" title="Direct link to Reproducibility checklist" translate="no">​</a></h2>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Raw images and metadata archived</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Mission and camera settings recorded</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Coordinate system and scale source documented</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Software versions and reconstruction parameters saved</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Plot geometry versioned</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Point-cloud filters and thresholds reported</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Independent ground truth retained</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Failed cases included in the report</li>
</ul>
<p><em>Workflow reviewed: July 2026. Parameters must be adapted and validated for each aircraft, crop, field, and research question.</em></p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="UAV" term="UAV"/>
        <category label="Remote Sensing" term="Remote Sensing"/>
        <category label="3D Reconstruction" term="3D Reconstruction"/>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Root Quantify: Interactive Root Image Preprocessing in Python]]></title>
        <id>https://smiler488.com/blog/root-quantify</id>
        <link href="https://smiler488.com/blog/root-quantify"/>
        <updated>2024-05-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A practical guide to using Root Quantify for polygon ROI selection, background correction, binary-mask cleanup, and organized export before downstream root analysis.]]></summary>
        <content type="html"><![CDATA[<p><strong>Root Quantify</strong> is a small OpenCV desktop utility for preparing root images. It guides a user through polygon selection, background correction, binarization, and manual cleanup, then saves the corrected region for analysis in another tool.</p>
<p>It is important to describe that boundary precisely: the current program creates cleaned binary images; it does <strong>not</strong> calculate validated root length, density, diameter, or architecture traits by itself.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="what-the-current-tool-does">What the current tool does<a href="https://smiler488.com/blog/root-quantify#what-the-current-tool-does" class="hash-link" aria-label="Direct link to What the current tool does" title="Direct link to What the current tool does" translate="no">​</a></h2>
<table><thead><tr><th>Stage</th><th>Operation</th><th>Result</th></tr></thead><tbody><tr><td>Folder scan</td><td>Finds JPG, JPEG, PNG, BMP, TIF, and TIFF files</td><td>A batch queue of source images</td></tr><tr><td>ROI selection</td><td>Records polygon vertices around the useful root region</td><td>A masked crop</td></tr><tr><td>Preprocessing</td><td>Estimates background, reduces uneven illumination, thresholds, and inverts the crop</td><td>Dark roots on a light background</td></tr><tr><td>Manual correction</td><td>Draws or erases pixels with an adjustable brush</td><td>A reviewed binary image</td></tr><tr><td>Export</td><td>Saves the corrected image and moves the original into an archive folder</td><td>No accidental reprocessing in the next run</td></tr></tbody></table>
<p>This workflow is most useful before skeletonization or measurement in software such as RhizoVision Explorer, WinRHIZO, ImageJ, or a validated laboratory pipeline.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="installation">Installation<a href="https://smiler488.com/blog/root-quantify#installation" class="hash-link" aria-label="Direct link to Installation" title="Direct link to Installation" translate="no">​</a></h2>
<p>The source is available in the <a href="https://github.com/smiler488/RootQuantify" target="_blank" rel="noopener noreferrer" class="">Root Quantify GitHub repository</a>.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone https://github.com/smiler488/RootQuantify.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd RootQuantify</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">source .venv/bin/activate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install --upgrade pip</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install -r requirements.txt</span><br></div></code></pre></div></div>
<p>On Windows, activate the environment with:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">.venv\Scripts\activate</span><br></div></code></pre></div></div>
<p>The interface requires a graphical desktop. A headless server or notebook session cannot display the OpenCV selection windows without additional display configuration.</p>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Configure the input directory</div><div class="admonitionContent_BuS1"><p>The current <code>RootImager.py</code> revision contains a <code>folder_path</code> value in the script. Set it to the directory containing the images before running. Keep a backup of that directory because completed originals are moved into <code>processed_original</code>.</p></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="run-the-workflow">Run the workflow<a href="https://smiler488.com/blog/root-quantify#run-the-workflow" class="hash-link" aria-label="Direct link to Run the workflow" title="Direct link to Run the workflow" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python RootImager.py</span><br></div></code></pre></div></div>
<p>Two windows are used: one keeps the original image visible, while the other handles ROI selection and correction.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="keyboard-controls">Keyboard controls<a href="https://smiler488.com/blog/root-quantify#keyboard-controls" class="hash-link" aria-label="Direct link to Keyboard controls" title="Direct link to Keyboard controls" translate="no">​</a></h3>
<table><thead><tr><th>Key</th><th>Context</th><th>Action</th></tr></thead><tbody><tr><td><code>c</code></td><td>ROI selection</td><td>Confirm a polygon with at least three vertices</td></tr><tr><td><code>r</code></td><td>ROI selection</td><td>Reset the polygon</td></tr><tr><td><code>d</code></td><td>Manual correction</td><td>Draw dark root pixels</td></tr><tr><td><code>e</code></td><td>Manual correction</td><td>Erase to a light background</td></tr><tr><td><code>+</code> / <code>-</code></td><td>Manual correction</td><td>Increase or decrease brush size</td></tr><tr><td><code>u</code></td><td>Manual correction</td><td>Undo the last completed stroke</td></tr><tr><td><code>q</code></td><td>Manual correction</td><td>Finish the current image</td></tr></tbody></table>
<p>After confirming the polygon, inspect the automatic threshold carefully. Correct only obvious segmentation errors; excessive manual editing reduces repeatability and should be recorded in the experiment log.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="inputs-and-outputs">Inputs and outputs<a href="https://smiler488.com/blog/root-quantify#inputs-and-outputs" class="hash-link" aria-label="Direct link to Inputs and outputs" title="Direct link to Inputs and outputs" translate="no">​</a></h2>
<p>The program writes corrected images to an <code>output</code> directory with a <code>processed-</code> filename prefix. It moves each completed source image into <code>processed_original</code>.</p>
<p>For reproducible work, save the following alongside the outputs:</p>
<ul>
<li class="">the unmodified original images in a separate read-only backup;</li>
<li class="">the Root Quantify commit hash;</li>
<li class="">the preprocessing parameters used in the script;</li>
<li class="">operator identity and correction date;</li>
<li class="">a note describing any difficult or excluded image.</li>
</ul>
<p>Do not use the moved copy as the only archive of raw data.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="quality-control-checklist">Quality-control checklist<a href="https://smiler488.com/blog/root-quantify#quality-control-checklist" class="hash-link" aria-label="Direct link to Quality-control checklist" title="Direct link to Quality-control checklist" translate="no">​</a></h2>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Roots and background have visibly different intensities.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->The ROI excludes labels, rulers, pot edges, and unrelated objects.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Fine lateral roots are retained after thresholding.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Shadows are not mistaken for roots.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Manual corrections are minimal and documented.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->A second reviewer checks a sample when measurements will support a publication.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Downstream measurements are validated against known objects or manual reference data.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="known-limitations">Known limitations<a href="https://smiler488.com/blog/root-quantify#known-limitations" class="hash-link" aria-label="Direct link to Known limitations" title="Direct link to Known limitations" translate="no">​</a></h2>
<ul>
<li class="">Threshold-based segmentation is sensitive to shadows, reflections, substrate, and overlapping roots.</li>
<li class="">A binary image discards color and intensity information from the original.</li>
<li class="">Manual correction introduces operator variability.</li>
<li class="">Moving source files is convenient for batching but requires a deliberate backup policy.</li>
<li class="">The desktop interaction is not designed for unattended or high-throughput server processing.</li>
<li class="">The output is a preprocessing result, not a biological conclusion or calibrated phenotype table.</li>
</ul>
<p>For a browser-based preprocessing workflow with different limits, see <a class="" href="https://smiler488.com/app/root-processor">Root Image Preprocessor</a> and its <a class="" href="https://smiler488.com/docs/tutorial-apps/root-preprocessor-tutorial">App Lab tutorial</a>.</p>
<p><em>Workflow reviewed: July 2026. Check the repository README and source before use because the interface may change.</em></p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Python" term="Python"/>
        <category label="Image Analysis" term="Image Analysis"/>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Local LLMs: A Reproducible Guide to Running, Fine-Tuning, and Serving Models]]></title>
        <id>https://smiler488.com/blog/local-llm-training-guide-en</id>
        <link href="https://smiler488.com/blog/local-llm-training-guide-en"/>
        <updated>2024-01-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A maintainable workflow for choosing a local model, testing it with Ollama, planning parameter-efficient fine-tuning, evaluating results, and exposing a service safely.]]></summary>
        <content type="html"><![CDATA[<p>Local large language models are useful when data must stay on controlled hardware, when offline operation matters, or when an experiment needs a fixed model and software stack. They are not automatically cheaper, faster, or more private: those outcomes depend on model size, hardware, network settings, and how the service is exposed.</p>
<p>This guide separates three jobs that are often mixed together: <strong>running a model</strong>, <strong>adapting a model</strong>, and <strong>serving a model</strong>. Start with the smallest job that answers your research question.</p>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Version-sensitive workflow</div><div class="admonitionContent_BuS1"><p>Model names, package APIs, CUDA builds, and hardware requirements change quickly. Record the model revision, package lockfile, operating system, driver, accelerator, prompt template, and test date for every reproducible experiment. Commands below are starting points, not universal production recipes.</p></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="choose-the-right-path">Choose the right path<a href="https://smiler488.com/blog/local-llm-training-guide-en#choose-the-right-path" class="hash-link" aria-label="Direct link to Choose the right path" title="Direct link to Choose the right path" translate="no">​</a></h2>
<table><thead><tr><th>Goal</th><th>Recommended starting point</th><th>Main risk</th></tr></thead><tbody><tr><td>Private interactive use</td><td>Run an existing quantized model locally</td><td>A model may still call networked tools or log prompts if configured to do so</td></tr><tr><td>Domain adaptation</td><td>LoRA or QLoRA on a curated dataset</td><td>Data leakage, memorization, and weak evaluation</td></tr><tr><td>Shared API</td><td>A dedicated inference server behind authentication</td><td>Unbounded cost, prompt abuse, and accidental public exposure</td></tr><tr><td>Pre-training from scratch</td><td>A separately designed research project</td><td>Very high compute, data governance, and evaluation burden</td></tr></tbody></table>
<p>For most individual researchers, local inference plus retrieval or a small LoRA experiment is more appropriate than full-parameter training.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-run-a-model-locally-with-ollama">1. Run a model locally with Ollama<a href="https://smiler488.com/blog/local-llm-training-guide-en#1-run-a-model-locally-with-ollama" class="hash-link" aria-label="Direct link to 1. Run a model locally with Ollama" title="Direct link to 1. Run a model locally with Ollama" translate="no">​</a></h2>
<p>Ollama provides a straightforward local runtime on macOS, Linux, and Windows. Use the current installer from the <a href="https://ollama.com/download" target="_blank" rel="noopener noreferrer" class="">official download page</a>; on systems where the shell installer is supported:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -fsSL https://ollama.com/install.sh | sh</span><br></div></code></pre></div></div>
<p>Browse the current model library, select a model that fits your memory budget, and replace <code>&lt;model-name&gt;</code> below:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ollama pull &lt;model-name&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ollama run &lt;model-name&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ollama list</span><br></div></code></pre></div></div>
<p>Do not select a model only by parameter count. Check its license, context length, supported languages, quantization, tool-calling format, and intended use.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="test-the-local-api">Test the local API<a href="https://smiler488.com/blog/local-llm-training-guide-en#test-the-local-api" class="hash-link" aria-label="Direct link to Test the local API" title="Direct link to Test the local API" translate="no">​</a></h3>
<p>Keep the service bound to the local machine during development. A minimal non-streaming request is:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl http://localhost:11434/api/chat -d '{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "model": "&lt;model-name&gt;",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "messages": [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    {"role": "user", "content": "Explain canopy photosynthesis in three sentences."}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "stream": false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}'</span><br></div></code></pre></div></div>
<p>Confirm that the process works offline if offline operation is a requirement. A downloaded model does not prove that every surrounding integration is offline.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-check-hardware-before-adapting-a-model">2. Check hardware before adapting a model<a href="https://smiler488.com/blog/local-llm-training-guide-en#2-check-hardware-before-adapting-a-model" class="hash-link" aria-label="Direct link to 2. Check hardware before adapting a model" title="Direct link to 2. Check hardware before adapting a model" translate="no">​</a></h2>
<p>Memory demand varies with model architecture, precision, optimizer, sequence length, batch size, and whether activations or optimizer states are offloaded. Avoid a single “minimum GPU” claim.</p>
<p>Use a small environment report instead:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> platform</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">report </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"platform"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> platform</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">platform</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"python"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> platform</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">python_version</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">try</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> torch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"torch"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">__version__</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"cuda_available"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"gpu"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get_device_name</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"gpu_memory_gb"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">round</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get_device_properties</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">total_memory </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1024</span><span class="token operator" style="color:#393A34">**</span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">except</span><span class="token plain"> ImportError</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    report</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"torch"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">json</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">dumps</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">report</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> indent</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Install PyTorch using the selector on the official site so the build matches the operating system and accelerator. Do not copy a fixed CUDA wheel command onto Apple Silicon, CPU-only, or differently versioned CUDA systems.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-prepare-data-before-fine-tuning">3. Prepare data before fine-tuning<a href="https://smiler488.com/blog/local-llm-training-guide-en#3-prepare-data-before-fine-tuning" class="hash-link" aria-label="Direct link to 3. Prepare data before fine-tuning" title="Direct link to 3. Prepare data before fine-tuning" translate="no">​</a></h2>
<p>A fine-tuning dataset should be traceable, licensed for the intended use, and split before experimentation.</p>
<ol>
<li class="">Define the task and a measurable success criterion.</li>
<li class="">Remove personal, confidential, copyrighted, and duplicated material that is not authorized for training.</li>
<li class="">Create train, validation, and test splits that prevent near-duplicate leakage.</li>
<li class="">Format examples using the selected model's chat template rather than inventing a generic prompt format.</li>
<li class="">Keep a dataset card with provenance, exclusions, transformations, and known biases.</li>
<li class="">Inspect a sample of rendered prompts and labels before starting a long run.</li>
</ol>
<p>For scientific work, split by the true unit of generalization. For example, specimens from the same plant, plot, site, or acquisition session should not be scattered across train and test sets when that would inflate performance.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-prefer-parameter-efficient-adaptation">4. Prefer parameter-efficient adaptation<a href="https://smiler488.com/blog/local-llm-training-guide-en#4-prefer-parameter-efficient-adaptation" class="hash-link" aria-label="Direct link to 4. Prefer parameter-efficient adaptation" title="Direct link to 4. Prefer parameter-efficient adaptation" translate="no">​</a></h2>
<p>LoRA and QLoRA update a small set of adapter parameters while keeping most base-model weights fixed. They generally reduce memory and storage requirements, but they do not guarantee better factuality or domain performance.</p>
<p>Use the current official instructions for a maintained training framework such as <a href="https://github.com/unslothai/unsloth" target="_blank" rel="noopener noreferrer" class="">Unsloth</a> or <a href="https://github.com/OpenAccess-AI-Collective/axolotl" target="_blank" rel="noopener noreferrer" class="">Axolotl</a>, and pin a known-working environment:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python -m venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">source .venv/bin/activate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install --upgrade pip</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip freeze &gt; requirements-lock.txt</span><br></div></code></pre></div></div>
<p>Your training record should include:</p>
<ul>
<li class="">base model and immutable revision;</li>
<li class="">tokenizer and chat template;</li>
<li class="">LoRA target modules, rank, alpha, and dropout;</li>
<li class="">sequence length, effective batch size, optimizer, and learning rate;</li>
<li class="">random seed and data split identifiers;</li>
<li class="">checkpoint selection rule and evaluation results;</li>
<li class="">wall time, accelerator type, and peak memory.</li>
</ul>
<p>Full-parameter fine-tuning and pre-training require a separate capacity plan, distributed-training validation, and substantially stronger data governance. They are intentionally outside this introductory workflow.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-evaluate-the-behavior-that-matters">5. Evaluate the behavior that matters<a href="https://smiler488.com/blog/local-llm-training-guide-en#5-evaluate-the-behavior-that-matters" class="hash-link" aria-label="Direct link to 5. Evaluate the behavior that matters" title="Direct link to 5. Evaluate the behavior that matters" translate="no">​</a></h2>
<p>Training loss alone is not evidence of a useful model. Build an evaluation set before training and keep it unchanged while selecting checkpoints.</p>
<table><thead><tr><th>Evaluation layer</th><th>Examples</th></tr></thead><tbody><tr><td>Task quality</td><td>Exact match, structured-output validity, domain rubric, retrieval faithfulness</td></tr><tr><td>Robustness</td><td>Paraphrases, missing context, conflicting evidence, long inputs</td></tr><tr><td>Safety</td><td>Sensitive-data recall, prompt injection, unsafe tool requests</td></tr><tr><td>Operations</td><td>Latency, throughput, peak memory, failure rate</td></tr><tr><td>Human review</td><td>Blinded pairwise preference with written criteria</td></tr></tbody></table>
<p>Report uncertainty and failure cases. BLEU or perplexity can be informative for specific tasks, but neither is a general measure of correctness or helpfulness.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-serve-locally-before-serving-a-network">6. Serve locally before serving a network<a href="https://smiler488.com/blog/local-llm-training-guide-en#6-serve-locally-before-serving-a-network" class="hash-link" aria-label="Direct link to 6. Serve locally before serving a network" title="Direct link to 6. Serve locally before serving a network" translate="no">​</a></h2>
<p>For a single workstation, the runtime's local API is often enough. For higher-throughput GPU serving, consult the current <a href="https://docs.vllm.ai/en/latest/models/supported_models.html" target="_blank" rel="noopener noreferrer" class="">vLLM supported-model documentation</a> and its deployment guide.</p>
<p>Before accepting remote requests, add at least:</p>
<ul>
<li class="">authentication and authorization;</li>
<li class="">request-size, token, concurrency, and rate limits;</li>
<li class="">explicit network binding and firewall rules;</li>
<li class="">timeouts, cancellation, and back-pressure;</li>
<li class="">structured logs that exclude prompts by default;</li>
<li class="">model and prompt-template version reporting;</li>
<li class="">health checks that distinguish “process alive” from “model ready”;</li>
<li class="">abuse testing and a rollback plan.</li>
</ul>
<p>Never expose an unauthenticated development server or a model dashboard directly to the public internet. A Gradio <code>share=True</code> tunnel is also a public endpoint, not a private local interface.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="troubleshooting-principles">Troubleshooting principles<a href="https://smiler488.com/blog/local-llm-training-guide-en#troubleshooting-principles" class="hash-link" aria-label="Direct link to Troubleshooting principles" title="Direct link to Troubleshooting principles" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="out-of-memory">Out of memory<a href="https://smiler488.com/blog/local-llm-training-guide-en#out-of-memory" class="hash-link" aria-label="Direct link to Out of memory" title="Direct link to Out of memory" translate="no">​</a></h3>
<p>Reduce sequence length and batch size first. Then consider gradient accumulation, checkpointing, quantization, adapter training, or a smaller model. Record every change because it can alter quality and speed.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="package-or-cuda-mismatch">Package or CUDA mismatch<a href="https://smiler488.com/blog/local-llm-training-guide-en#package-or-cuda-mismatch" class="hash-link" aria-label="Direct link to Package or CUDA mismatch" title="Direct link to Package or CUDA mismatch" translate="no">​</a></h3>
<p>Create a clean environment and verify the driver, CUDA runtime, PyTorch build, and optional attention kernels independently. Optional acceleration libraries should not be installed until the basic model can load.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="training-loss-does-not-improve">Training loss does not improve<a href="https://smiler488.com/blog/local-llm-training-guide-en#training-loss-does-not-improve" class="hash-link" aria-label="Direct link to Training loss does not improve" title="Direct link to Training loss does not improve" translate="no">​</a></h3>
<p>Inspect rendered examples and labels, compare against a small overfitting test, verify the learning rate, and confirm that adapter parameters receive gradients. More steps cannot repair malformed data.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="good-benchmark-poor-real-use">Good benchmark, poor real use<a href="https://smiler488.com/blog/local-llm-training-guide-en#good-benchmark-poor-real-use" class="hash-link" aria-label="Direct link to Good benchmark, poor real use" title="Direct link to Good benchmark, poor real use" translate="no">​</a></h3>
<p>Check leakage, prompt-template differences, retrieval quality, and whether the test set represents the actual deployment population. Add examples from observed failures only to a new development set, not retroactively to the held-out test set.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="reproducibility-checklist">Reproducibility checklist<a href="https://smiler488.com/blog/local-llm-training-guide-en#reproducibility-checklist" class="hash-link" aria-label="Direct link to Reproducibility checklist" title="Direct link to Reproducibility checklist" translate="no">​</a></h2>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Model license and revision recorded</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Dataset provenance and split strategy documented</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Dependencies locked</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Hardware and runtime recorded</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Prompts and generation settings versioned</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Baseline evaluated before adaptation</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Held-out test set preserved</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Security and privacy boundaries tested</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Limitations and failed cases reported</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="further-reading">Further reading<a href="https://smiler488.com/blog/local-llm-training-guide-en#further-reading" class="hash-link" aria-label="Direct link to Further reading" title="Direct link to Further reading" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://ollama.com/download" target="_blank" rel="noopener noreferrer" class="">Ollama documentation</a></li>
<li class=""><a href="https://docs.vllm.ai/en/latest/models/supported_models.html" target="_blank" rel="noopener noreferrer" class="">vLLM supported models</a></li>
<li class=""><a href="https://github.com/unslothai/unsloth" target="_blank" rel="noopener noreferrer" class="">Unsloth</a></li>
<li class=""><a href="https://github.com/OpenAccess-AI-Collective/axolotl" target="_blank" rel="noopener noreferrer" class="">Axolotl</a></li>
<li class=""><a href="https://github.com/oobabooga/text-generation-webui" target="_blank" rel="noopener noreferrer" class="">Text Generation WebUI</a></li>
<li class=""><a href="https://huggingface.co/Qwen/Qwen2-7B" target="_blank" rel="noopener noreferrer" class="">Hugging Face model hub</a></li>
</ul>
<p><em>Workflow reviewed: July 2026. Re-check upstream documentation before reproducing the commands.</em></p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Artificial Intelligence" term="Artificial Intelligence"/>
        <category label="Machine Learning" term="Machine Learning"/>
        <category label="Local AI" term="Local AI"/>
        <category label="Reproducible Research" term="Reproducible Research"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Canopy Photosynthesis from 3D Plant Models — A Conceptual Workflow]]></title>
        <id>https://smiler488.com/blog/canopy-photosynthesis-modeling-en</id>
        <link href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en"/>
        <updated>2023-08-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A validation-first research framework connecting multi-view reconstruction, plant geometry, radiative transfer, leaf physiology, and canopy-scale uncertainty.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="project-overview">Project overview<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#project-overview" class="hash-link" aria-label="Direct link to Project overview" title="Direct link to Project overview" translate="no">​</a></h2>
<p>Canopy photosynthesis modeling links several distinct systems: image acquisition, 3D reconstruction, organ geometry, radiative transfer, leaf physiology, and environmental forcing. This article defines the interfaces and validation requirements between those stages.</p>
<p>It is a <strong>conceptual research workflow</strong>, not a complete executable simulator. Each stage requires a tested implementation, calibrated parameters, units, uncertainty analysis, and comparison with independent measurements.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="workflow-map">Workflow map<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#workflow-map" class="hash-link" aria-label="Direct link to Workflow map" title="Direct link to Workflow map" translate="no">​</a></h2>
<!-- -->
<p>The arrows are data contracts. For example, a radiative-transfer model needs geometry with known units and normals; a photosynthesis model needs absorbed radiation in physical units, not arbitrary ray-hit counts.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-define-the-scientific-question-first">1. Define the scientific question first<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#1-define-the-scientific-question-first" class="hash-link" aria-label="Direct link to 1. Define the scientific question first" title="Direct link to 1. Define the scientific question first" translate="no">​</a></h2>
<p>Possible questions include:</p>
<ul>
<li class="">How does leaf-angle distribution affect within-canopy light?</li>
<li class="">Which architectural traits explain differences in daily carbon gain?</li>
<li class="">Can reconstructed canopies reproduce measured light interception?</li>
<li class="">How sensitive is a crop-model parameter to 3D structural variation?</li>
</ul>
<p>The question determines the necessary spatial and temporal resolution. A visualization model does not automatically support quantitative carbon-flux claims.</p>
<p>Pre-register or document:</p>
<ul>
<li class="">target variable and unit;</li>
<li class="">spatial unit: leaf, plant, plot, or canopy;</li>
<li class="">temporal unit: instant, hour, or day;</li>
<li class="">experimental factors and replicates;</li>
<li class="">calibration and validation observations;</li>
<li class="">acceptable error or decision threshold.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-acquire-multi-view-images">2. Acquire multi-view images<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#2-acquire-multi-view-images" class="hash-link" aria-label="Direct link to 2. Acquire multi-view images" title="Direct link to 2. Acquire multi-view images" translate="no">​</a></h2>
<p>Use a capture protocol appropriate to the plant scale:</p>
<ul>
<li class="">stable illumination and minimal leaf motion;</li>
<li class="">locked or recorded focus, exposure, white balance, and focal length;</li>
<li class="">sufficient overlap and multiple elevations;</li>
<li class="">measured scale controls;</li>
<li class="">calibration images and camera metadata;</li>
<li class="">subject identifiers that connect images to field or chamber records.</li>
</ul>
<p>Turntable and field capture need different background strategies. In a turntable setup, static room features must be masked because the plant rotates relative to them. In field capture, wind and moving shadows can violate the static-scene assumption.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="acquisition-validation">Acquisition validation<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#acquisition-validation" class="hash-link" aria-label="Direct link to Acquisition validation" title="Direct link to Acquisition validation" translate="no">​</a></h3>
<p>Report image count, rejected images, angular or spatial coverage, blur screening, marker visibility, and missing views. Repeat a subset of plants to quantify capture repeatability.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-reconstruct-geometry">3. Reconstruct geometry<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#3-reconstruct-geometry" class="hash-link" aria-label="Direct link to 3. Reconstruct geometry" title="Direct link to 3. Reconstruct geometry" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="structure-from-motion-and-multi-view-stereo">Structure from Motion and multi-view stereo<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#structure-from-motion-and-multi-view-stereo" class="hash-link" aria-label="Direct link to Structure from Motion and multi-view stereo" title="Direct link to Structure from Motion and multi-view stereo" translate="no">​</a></h3>
<p>SfM estimates camera poses and sparse scene structure; multi-view stereo creates a denser point cloud. Tools such as COLMAP can provide these stages, but command-line option names change across versions. Save the exact software version and configuration.</p>
<p>The geometry pipeline should produce:</p>
<ul>
<li class="">camera poses and intrinsics;</li>
<li class="">scaled point cloud or mesh;</li>
<li class="">per-vertex or per-face normals;</li>
<li class="">confidence or density information where available;</li>
<li class="">reconstruction diagnostics.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3d-gaussian-splatting">3D Gaussian Splatting<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#3d-gaussian-splatting" class="hash-link" aria-label="Direct link to 3D Gaussian Splatting" title="Direct link to 3D Gaussian Splatting" translate="no">​</a></h3>
<p>3D Gaussian Splatting is primarily a learned radiance-field representation for novel-view rendering. A set of optimized Gaussians is not automatically a watertight, metrically validated plant mesh. Geometry extraction requires an additional, documented method and its own validation.</p>
<p>Do not replace an SfM-to-mesh pipeline with a few tensor parameters and call the result complete 3DGS. A practical implementation needs a differentiable rasterizer, constrained scale/rotation/opacity parameterization, densification and pruning, camera calibration, and a defined export path.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-validate-and-process-the-mesh">4. Validate and process the mesh<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#4-validate-and-process-the-mesh" class="hash-link" aria-label="Direct link to 4. Validate and process the mesh" title="Direct link to 4. Validate and process the mesh" translate="no">​</a></h2>
<p>Thin leaves, self-occlusion, texture-poor surfaces, and motion produce holes and false surfaces. Surface reconstruction can also fill unsupported regions.</p>
<p>Inspect:</p>
<ul>
<li class="">scale error against independent distances;</li>
<li class="">completeness by organ and canopy depth;</li>
<li class="">false connections between overlapping leaves;</li>
<li class="">mesh normals and orientation;</li>
<li class="">non-manifold or degenerate geometry;</li>
<li class="">sensitivity to masking and reconstruction settings.</li>
</ul>
<p>Poisson reconstruction returns a smooth closed surface and can extrapolate into low-density areas. Use density information and measured evidence to crop unsupported surfaces rather than accepting the default mesh.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-derive-plant-organs-and-leaf-area">5. Derive plant organs and leaf area<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#5-derive-plant-organs-and-leaf-area" class="hash-link" aria-label="Direct link to 5. Derive plant organs and leaf area" title="Direct link to 5. Derive plant organs and leaf area" translate="no">​</a></h2>
<p>A curvature threshold alone is not a robust leaf/stem segmentation method. Curvature depends on scale, mesh resolution, noise, and neighborhood definition.</p>
<p>Use a validated combination of:</p>
<ul>
<li class="">geometry and topology;</li>
<li class="">color or spectral information;</li>
<li class="">supervised organ labels;</li>
<li class="">plant-development constraints;</li>
<li class="">manual review on a representative subset.</li>
</ul>
<p>For each organ, preserve:</p>
<ul>
<li class="">surface area and the method used to calculate it;</li>
<li class="">orientation and normal convention;</li>
<li class="">thickness or two-sided-leaf assumption;</li>
<li class="">segmentation confidence;</li>
<li class="">link to the original plant and treatment.</li>
</ul>
<p>Compare total reconstructed leaf area with destructive leaf-area measurements or another independent method.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-build-a-measured-or-synthetic-canopy">6. Build a measured or synthetic canopy<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#6-build-a-measured-or-synthetic-canopy" class="hash-link" aria-label="Direct link to 6. Build a measured or synthetic canopy" title="Direct link to 6. Build a measured or synthetic canopy" translate="no">​</a></h2>
<p>There are two different scientific products:</p>
<ol>
<li class=""><strong>Measured canopy:</strong> positions and geometry are reconstructed from an observed stand.</li>
<li class=""><strong>Synthetic canopy:</strong> plant instances and traits are generated to test hypotheses.</li>
</ol>
<p>Synthetic perturbations must be interpretable. Record the distribution, covariance, bounds, and random seed for plant spacing, height, azimuth, leaf angle, and size. Repeatedly cloning one plant and adding arbitrary vertex noise does not reproduce population-level architectural diversity.</p>
<p>Check for plant overlap, below-ground geometry, unrealistic leaf intersections, and changes in leaf area after transformation.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-model-the-light-environment-in-physical-units">7. Model the light environment in physical units<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#7-model-the-light-environment-in-physical-units" class="hash-link" aria-label="Direct link to 7. Model the light environment in physical units" title="Direct link to 7. Model the light environment in physical units" translate="no">​</a></h2>
<p>A radiative-transfer stage needs:</p>
<ul>
<li class="">canopy coordinates and units;</li>
<li class="">direct and diffuse sky radiation;</li>
<li class="">sun position calculated from date, time, and location;</li>
<li class="">leaf reflectance and transmittance by waveband;</li>
<li class="">soil/background optical properties;</li>
<li class="">an absorption and scattering model;</li>
<li class="">a mapping from intercepted energy to absorbed PPFD or another defined quantity.</li>
</ul>
<p>Angles provided in degrees must be converted before using NumPy trigonometric functions:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> numpy </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> np</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">elevation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">deg2rad</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">elevation_degrees</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">azimuth </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">deg2rad</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">azimuth_degrees</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Counting first ray intersections produces a sampling density, not irradiance. Each ray needs a defined energy or probability weight, and the model must describe whether it includes transmission, reflection, multiple scattering, and diffuse radiation.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="light-model-validation">Light-model validation<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#light-model-validation" class="hash-link" aria-label="Direct link to Light-model validation" title="Direct link to Light-model validation" translate="no">​</a></h3>
<p>Compare predictions with independent observations such as:</p>
<ul>
<li class="">above- and below-canopy PAR sensors;</li>
<li class="">vertical or horizontal light profiles;</li>
<li class="">intercepted-radiation measurements;</li>
<li class="">hemispherical images or ceptometers;</li>
<li class="">energy conservation and convergence checks.</li>
</ul>
<p>Increase ray count or spatial resolution until the output of interest converges within a predefined tolerance.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="8-parameterize-leaf-photosynthesis">8. Parameterize leaf photosynthesis<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#8-parameterize-leaf-photosynthesis" class="hash-link" aria-label="Direct link to 8. Parameterize leaf photosynthesis" title="Direct link to 8. Parameterize leaf photosynthesis" translate="no">​</a></h2>
<p>The Farquhar–von Caemmerer–Berry model describes C3 leaf photosynthesis through biochemical limitations. A credible implementation needs more than a generic <code>Vcmax25</code> and <code>Jmax25</code>.</p>
<p>Define and justify:</p>
<ul>
<li class="">species, cultivar, leaf age, and nitrogen status;</li>
<li class="">leaf temperature rather than air temperature when they differ;</li>
<li class="">intercellular or chloroplastic CO₂ treatment;</li>
<li class="">Rubisco-, electron-transport-, and, when relevant, TPU-limited rates;</li>
<li class="">day respiration;</li>
<li class="">temperature-response functions;</li>
<li class="">stomatal and, when needed, mesophyll conductance;</li>
<li class="">absorbed light and spectral assumptions;</li>
<li class="">fitted parameter uncertainty.</li>
</ul>
<p>Ambient CO₂ concentration cannot simply replace intercellular CO₂ without a conductance model or an explicitly justified approximation.</p>
<p>Fit physiological parameters from appropriate gas-exchange observations or cite a parameter source that matches the biological material and conditions. Generic literature constants are useful for sensitivity analysis, not automatically for quantitative prediction.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="9-integrate-leaf-rates-without-losing-units">9. Integrate leaf rates without losing units<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#9-integrate-leaf-rates-without-losing-units" class="hash-link" aria-label="Direct link to 9. Integrate leaf rates without losing units" title="Direct link to 9. Integrate leaf rates without losing units" translate="no">​</a></h2>
<p>For leaf element <code>i</code> with net assimilation <code>A_i</code> in micromoles of CO₂ per square metre per second and one-sided area <code>a_i</code> in square metres:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">P_canopy = sum_i(A_i × a_i)</span><br></div></code></pre></div></div>
<p>The result is micromoles of CO₂ per second. An area-weighted mean is:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">A_mean = sum_i(A_i × a_i) / sum_i(a_i)</span><br></div></code></pre></div></div>
<p>Dividing total assimilation by the number of occupied voxels does not produce an area-based rate.</p>
<p>For daily carbon gain, integrate over time using environmental forcing and consistent time units. Report whether the canopy domain represents one plant, one row segment, one square metre of ground, or a whole plot.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="10-keep-the-implementation-modular">10. Keep the implementation modular<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#10-keep-the-implementation-modular" class="hash-link" aria-label="Direct link to 10. Keep the implementation modular" title="Direct link to 10. Keep the implementation modular" translate="no">​</a></h2>
<p>A reproducible project can expose explicit stage interfaces:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">images + calibration</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    -&gt; camera poses + scaled geometry</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">geometry + organ labels</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    -&gt; leaf elements + area + normals</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">leaf elements + environment + optics</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    -&gt; absorbed PPFD by element and time</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">absorbed PPFD + physiology</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    -&gt; leaf assimilation by element and time</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">leaf assimilation + area</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    -&gt; canopy exchange + uncertainty</span><br></div></code></pre></div></div>
<p>For every interface, validate:</p>
<ul>
<li class="">file schema and coordinate convention;</li>
<li class="">shape, unit, range, and missing values;</li>
<li class="">provenance and software version;</li>
<li class="">stable identifiers across stages;</li>
<li class="">a small fixture with a known or manually checked result.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="11-uncertainty-and-sensitivity">11. Uncertainty and sensitivity<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#11-uncertainty-and-sensitivity" class="hash-link" aria-label="Direct link to 11. Uncertainty and sensitivity" title="Direct link to 11. Uncertainty and sensitivity" translate="no">​</a></h2>
<p>Uncertainty enters through:</p>
<ul>
<li class="">camera calibration and scale;</li>
<li class="">missing or falsely reconstructed leaf area;</li>
<li class="">organ segmentation;</li>
<li class="">leaf normals and optical properties;</li>
<li class="">sky and sun forcing;</li>
<li class="">physiological parameter fitting;</li>
<li class="">numerical sampling and time integration.</li>
</ul>
<p>Use perturbation, Monte Carlo, or a designed sensitivity analysis to determine which uncertainties control the final canopy result. Report distributions or intervals, not only a single simulated value.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="minimum-evidence-for-a-quantitative-claim">Minimum evidence for a quantitative claim<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#minimum-evidence-for-a-quantitative-claim" class="hash-link" aria-label="Direct link to Minimum evidence for a quantitative claim" title="Direct link to Minimum evidence for a quantitative claim" translate="no">​</a></h2>
<ul>
<li class="">independent geometric measurements;</li>
<li class="">measured or well-supported optical properties;</li>
<li class="">light-model validation;</li>
<li class="">physiological calibration or appropriate parameter provenance;</li>
<li class="">canopy-level gas-exchange or biomass/carbon evidence when available;</li>
<li class="">repeatability across plants or plots;</li>
<li class="">a fully versioned environment, configuration, and data manifest.</li>
</ul>
<p>Without this evidence, present the output as a hypothesis-generating simulation or visualization.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="references-and-implementations">References and implementations<a href="https://smiler488.com/blog/canopy-photosynthesis-modeling-en#references-and-implementations" class="hash-link" aria-label="Direct link to References and implementations" title="Direct link to References and implementations" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://colmap.github.io/cli.html" target="_blank" rel="noopener noreferrer" class="">COLMAP command-line interface</a></li>
<li class=""><a href="https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/" target="_blank" rel="noopener noreferrer" class="">3D Gaussian Splatting paper and reference implementation</a></li>
<li class=""><a href="https://www.open3d.org/docs/latest/tutorial/Advanced/surface_reconstruction.html" target="_blank" rel="noopener noreferrer" class="">Open3D surface reconstruction guide</a></li>
<li class=""><a href="https://biocycle.atmos.colostate.edu/Documents/SiB/Farquhar_1980.pdf" target="_blank" rel="noopener noreferrer" class="">Farquhar, von Caemmerer, and Berry (1980)</a></li>
</ul>
<p>These resources document individual stages. They do not remove the need to validate the integrated crop or canopy model for the intended experiment.</p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Crop Modeling" term="Crop Modeling"/>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
        <category label="3D Reconstruction" term="3D Reconstruction"/>
        <category label="Computer Vision" term="Computer Vision"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[DJI P4 Multispectral to Plot-Level Traits with WebODM and QGIS]]></title>
        <id>https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow</id>
        <link href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow"/>
        <updated>2023-04-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A validation-first workflow for processing DJI P4 Multispectral imagery in WebODM, checking band metadata in QGIS, and extracting plot-level vegetation features.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="project-overview">Project overview<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#project-overview" class="hash-link" aria-label="Direct link to Project overview" title="Direct link to Project overview" translate="no">​</a></h2>
<p>This guide takes original DJI P4 Multispectral imagery through WebODM and QGIS to a plot-level feature table. It distinguishes digital numbers from calibrated reflectance, treats band order and coordinate reference systems as data to verify, and keeps NoData pixels out of vegetation and texture statistics.</p>
<ul>
<li class=""><strong>Inputs:</strong> original RGB and multispectral captures, metadata, plot boundaries, and preferably calibration and accuracy controls</li>
<li class=""><strong>Outputs:</strong> checked orthomosaic bands, vegetation indices, optional texture layers, and plot-level statistics</li>
<li class=""><strong>Boundary:</strong> menu labels vary by WebODM and QGIS version; verify the installed interface and processing report</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-preserve-the-source-data">1. Preserve the source data<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#1-preserve-the-source-data" class="hash-link" aria-label="Direct link to 1. Preserve the source data" title="Direct link to 1. Preserve the source data" translate="no">​</a></h2>
<p>A P4 Multispectral capture normally contains one RGB image and five narrow bands: blue, green, red, red edge, and near infrared. Preserve the camera-generated files and metadata as a read-only source dataset.</p>
<p>Before processing:</p>
<ul>
<li class="">copy, rather than move, the original folders;</li>
<li class="">keep all bands from each capture together;</li>
<li class="">preserve EXIF/XMP metadata, exposure information, and camera filenames;</li>
<li class="">check that every capture has the expected set of bands;</li>
<li class="">record flight, illumination, calibration-panel, RTK/GNSS, and ground-control information;</li>
<li class="">calculate checksums when data integrity matters.</li>
</ul>
<p>Do not rely on the downwelling light sensor alone as proof of reflectance accuracy. Calibration-panel observations, stable acquisition conditions, and independent validation strengthen the radiometric record.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-install-and-start-webodm">2. Install and start WebODM<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#2-install-and-start-webodm" class="hash-link" aria-label="Direct link to 2. Install and start WebODM" title="Direct link to 2. Install and start WebODM" translate="no">​</a></h2>
<p>WebODM uses Docker. Install Docker Desktop and Git, allocate sufficient memory and storage, then clone the official repository:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone https://github.com/OpenDroneMap/WebODM</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd WebODM</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">./webodm.sh start</span><br></div></code></pre></div></div>
<p>Open <code>http://localhost:8000</code> and create the local administrator account on first use.</p>
<p>Follow the current WebODM installation documentation for operating-system-specific prerequisites. Avoid copying unverified third-party registry mirrors into Docker configuration; availability and trust can change.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-create-a-multispectral-task">3. Create a multispectral task<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#3-create-a-multispectral-task" class="hash-link" aria-label="Direct link to 3. Create a multispectral task" title="Direct link to 3. Create a multispectral task" translate="no">​</a></h2>
<ol>
<li class="">Create a project with a stable identifier, such as <code>maize-n-trial-2025</code>.</li>
<li class="">Create a task and upload all bands together.</li>
<li class="">Select the multispectral preset when it is available.</li>
<li class="">Save the exact WebODM/ODM version and task options with the results.</li>
</ol>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="radiometric-calibration">Radiometric calibration<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#radiometric-calibration" class="hash-link" aria-label="Direct link to Radiometric calibration" title="Direct link to Radiometric calibration" translate="no">​</a></h3>
<p>OpenDroneMap exposes three radiometric modes:</p>
<ul>
<li class=""><code>none</code> — output remains in sensor digital-number space;</li>
<li class=""><code>camera</code> — applies supported camera corrections when the required metadata are present;</li>
<li class=""><code>camera+sun</code> — additionally uses a downwelling-light-sensor signal and sun geometry; the ODM documentation labels this mode experimental.</li>
</ul>
<p>Choose a mode deliberately and describe it in the methods. Do not label an output “reflectance” when calibration was <code>none</code>.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="band-alignment-and-reconstruction">Band alignment and reconstruction<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#band-alignment-and-reconstruction" class="hash-link" aria-label="Direct link to Band alignment and reconstruction" title="Direct link to Band alignment and reconstruction" translate="no">​</a></h3>
<ul>
<li class="">ODM aligns multispectral bands by default. Do <strong>not</strong> enable <code>skip-band-alignment</code> unless the input has already been aligned and that fact has been verified.</li>
<li class="">Leave the primary band on automatic selection or choose a sharp, well-focused band after inspection.</li>
<li class="">Use fixed camera parameters only when the calibration and camera model justify that constraint.</li>
<li class="">The <code>use-exif</code> option is not a general “turn GPS on” switch. ODM already reads image metadata; <code>use-exif</code> is relevant when a GCP file is supplied but EXIF georeferencing should also be used.</li>
<li class="">If only 2D products are required, <code>skip-3dmodel</code> can reduce unnecessary output. Do not assume that every intermediate point-cloud calculation can be skipped.</li>
</ul>
<p>For survey accuracy, use well-distributed ground control and independent checkpoints when the research question requires them. RTK image positions do not remove the need to validate the final map.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-inspect-the-webodm-outputs">4. Inspect the WebODM outputs<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#4-inspect-the-webodm-outputs" class="hash-link" aria-label="Direct link to 4. Inspect the WebODM outputs" title="Direct link to 4. Inspect the WebODM outputs" translate="no">​</a></h2>
<p>Download the orthophoto and processing report from the task assets. The displayed download name can vary; do not assume the file is always called <code>orthophoto.tif</code>.</p>
<p>Before calculating an index, record:</p>
<ul>
<li class="">raster dimensions, data type, and NoData value;</li>
<li class="">CRS and pixel size;</li>
<li class="">number of bands and each band description;</li>
<li class="">whether values are digital numbers or reflectance-like calibrated values;</li>
<li class="">processing warnings and geolocation/checkpoint errors.</li>
</ul>
<p>Use <code>gdalinfo</code> or <strong>QGIS → Layer Properties → Information</strong>. Never infer red and NIR band numbers from an example on the internet.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-set-up-the-qgis-project">5. Set up the QGIS project<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#5-set-up-the-qgis-project" class="hash-link" aria-label="Direct link to 5. Set up the QGIS project" title="Direct link to 5. Set up the QGIS project" translate="no">​</a></h2>
<p>Add the multiband orthomosaic and plot-boundary layer to QGIS.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="choose-the-crs-from-the-survey-location">Choose the CRS from the survey location<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#choose-the-crs-from-the-survey-location" class="hash-link" aria-label="Direct link to Choose the CRS from the survey location" title="Direct link to Choose the CRS from the survey location" translate="no">​</a></h3>
<p>Spatial operations and plot areas should use an appropriate projected CRS with linear units. Determine the UTM zone or local projected CRS from the actual survey location. A copied example such as <code>EPSG:32645</code> is correct only for data located in that zone.</p>
<p>Reproject the plot layer when necessary; assigning a new CRS without transforming coordinates does not reproject data.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="clip-without-losing-nodata">Clip without losing NoData<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#clip-without-losing-nodata" class="hash-link" aria-label="Direct link to Clip without losing NoData" title="Direct link to Clip without losing NoData" translate="no">​</a></h3>
<p>Use <strong>Clip raster by mask layer</strong> with the field boundary:</p>
<ul>
<li class="">enable crop to cutline;</li>
<li class="">select a defined NoData value;</li>
<li class="">preserve the original raster;</li>
<li class="">confirm the clipped edge and pixel alignment visually.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-calculate-ndvi-safely">6. Calculate NDVI safely<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#6-calculate-ndvi-safely" class="hash-link" aria-label="Direct link to 6. Calculate NDVI safely" title="Direct link to 6. Calculate NDVI safely" translate="no">​</a></h2>
<p>NDVI is:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">NDVI = (NIR - Red) / (NIR + Red)</span><br></div></code></pre></div></div>
<p>After verifying the band numbers, replace the placeholders in the QGIS Raster Calculator:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">("orthomosaic@&lt;NIR band&gt;" - "orthomosaic@&lt;Red band&gt;") /</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">("orthomosaic@&lt;NIR band&gt;" + "orthomosaic@&lt;Red band&gt;")</span><br></div></code></pre></div></div>
<p>Also create a valid-data mask that excludes source NoData and zero denominators. Save the output as Float32 and inspect:</p>
<ul>
<li class="">expected field patterns;</li>
<li class="">values outside [-1, 1];</li>
<li class="">seams, shadows, saturation, and uncalibrated exposure differences;</li>
<li class="">consistency with known vegetation and bare-soil areas.</li>
</ul>
<p>The valid NDVI range does not make every value biologically plausible. Interpretation depends on crop, growth stage, atmosphere, soil, viewing geometry, and calibration.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-build-a-vegetation-mask">7. Build a vegetation mask<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#7-build-a-vegetation-mask" class="hash-link" aria-label="Direct link to 7. Build a vegetation mask" title="Direct link to 7. Build a vegetation mask" translate="no">​</a></h2>
<p>A threshold such as 0.2 is an experiment-specific starting point, not a universal vegetation boundary. Select it using representative labelled pixels or a documented sensitivity analysis.</p>
<p>Keep two separate rasters:</p>
<ol>
<li class="">a binary vegetation mask;</li>
<li class="">NDVI with non-vegetation pixels set to <strong>NoData</strong>, not zero.</li>
</ol>
<p>Multiplying NDVI by a binary mask sets the background to zero. Those zeros then enter means and, if the raster is shifted for quantization, can become mid-grey values. Use an explicit NoData mask before zonal statistics or texture calculation.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="8-quantize-only-when-a-texture-method-requires-it">8. Quantize only when a texture method requires it<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#8-quantize-only-when-a-texture-method-requires-it" class="hash-link" aria-label="Direct link to 8. Quantize only when a texture method requires it" title="Direct link to 8. Quantize only when a texture method requires it" translate="no">​</a></h2>
<p>GRASS <code>r.texture</code> expects integer grey levels. Document the input layer, valid range, scale, offset, rounding, and NoData handling.</p>
<p>If the complete NDVI interval ([-1, 1]) is intentionally mapped to UInt8, the conceptual transform is:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">UInt8 = round((NDVI + 1) × 127.5)</span><br></div></code></pre></div></div>
<p>Clamp only valid vegetation pixels to 0–255 and leave background as NoData. A data-driven range can improve contrast, but it changes comparability between dates unless the same documented limits are used.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="9-calculate-glcm-texture-deliberately">9. Calculate GLCM texture deliberately<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#9-calculate-glcm-texture-deliberately" class="hash-link" aria-label="Direct link to 9. Calculate GLCM texture deliberately" title="Direct link to 9. Calculate GLCM texture deliberately" translate="no">​</a></h2>
<p>Enable the GRASS processing provider before using <code>r.texture</code>. Common outputs include:</p>
<ul>
<li class=""><code>asm</code> — angular second moment;</li>
<li class=""><code>contrast</code> — local grey-level contrast;</li>
<li class=""><code>corr</code> — correlation;</li>
<li class=""><code>idm</code> — inverse difference moment;</li>
<li class=""><code>entr</code> — entropy.</li>
</ul>
<p><code>sa</code> is sum average, not another name for ASM, and <code>dv</code> is difference variance.</p>
<p>Record:</p>
<ul>
<li class="">window size in pixels and its footprint in metres;</li>
<li class="">pixel distance;</li>
<li class="">direction or directional aggregation;</li>
<li class="">quantization rule;</li>
<li class="">edge and NoData behavior.</li>
</ul>
<p>Choose the window from ground sampling distance and the biological scale of interest. A 7-pixel window at 2 cm GSD represents a different canopy area than the same window at 10 cm GSD.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="10-extract-plot-level-statistics">10. Extract plot-level statistics<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#10-extract-plot-level-statistics" class="hash-link" aria-label="Direct link to 10. Extract plot-level statistics" title="Direct link to 10. Extract plot-level statistics" translate="no">​</a></h2>
<p>Run <strong>Zonal statistics</strong> with the plot polygons and a stable <code>plot_id</code>.</p>
<p>Useful summaries include:</p>
<ul>
<li class="">valid-pixel count and coverage fraction;</li>
<li class="">mean, median, standard deviation, and selected quantiles;</li>
<li class="">vegetation fraction;</li>
<li class="">selected texture metrics.</li>
</ul>
<p>Exclude NoData and flag plots with too little valid vegetation. Export the attribute table to CSV together with processing metadata, not as an unexplained standalone table.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="validation-checklist">Validation checklist<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#validation-checklist" class="hash-link" aria-label="Direct link to Validation checklist" title="Direct link to Validation checklist" translate="no">​</a></h2>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Every capture contains the expected bands and metadata.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->The radiometric mode and calibration evidence are recorded.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Band descriptions were checked before index calculation.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->CRS and ground sampling distance match the survey.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Ground-control and independent-check errors are reported when applicable.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Background pixels are NoData rather than numeric zeros in trait statistics.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Threshold and texture parameters were tested for sensitivity.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Plot coverage and valid-pixel counts accompany each summary.</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->Software versions, task options, and formulas are archived.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="references">References<a href="https://smiler488.com/blog/dji-p4m-webodm-qgis-workflow#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://docs.opendronemap.org/multispectral/" target="_blank" rel="noopener noreferrer" class="">OpenDroneMap multispectral and thermal processing</a></li>
<li class=""><a href="https://docs.opendronemap.org/arguments/radiometric-calibration/" target="_blank" rel="noopener noreferrer" class="">ODM radiometric calibration option</a></li>
<li class=""><a href="https://docs.opendronemap.org/arguments/" target="_blank" rel="noopener noreferrer" class="">ODM options and flags</a></li>
<li class=""><a href="https://docs.qgis.org/" target="_blank" rel="noopener noreferrer" class="">QGIS documentation</a></li>
</ul>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="UAV" term="UAV"/>
        <category label="Remote Sensing" term="Remote Sensing"/>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
        <category label="Data Analysis" term="Data Analysis"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[PyTorch for Research — A Version-Aware Learning Roadmap]]></title>
        <id>https://smiler488.com/blog/pytorch-ml-dl-tutorial</id>
        <link href="https://smiler488.com/blog/pytorch-ml-dl-tutorial"/>
        <updated>2022-09-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A concise, reproducibility-focused route from tensors and training loops to transfer learning, evaluation, safe checkpoints, and deployment boundaries.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="project-overview">Project overview<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#project-overview" class="hash-link" aria-label="Direct link to Project overview" title="Direct link to Project overview" translate="no">​</a></h2>
<p>This article is a learning roadmap, not a copy-and-run production framework. It keeps one small executable example, then explains the decisions that make a research model auditable: device handling, seeds, data splits, metrics, checkpoints, version records, and validation.</p>
<ul>
<li class=""><strong>Audience:</strong> Python users starting reproducible machine-learning experiments</li>
<li class=""><strong>API scope:</strong> recent PyTorch 2.x and torchvision releases; always check the installed-version documentation</li>
<li class=""><strong>Verification boundary:</strong> examples are intentionally small and syntax-checkable; no benchmark, cloud price, or hardware-performance claim is implied</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-install-from-the-official-selector">1. Install from the official selector<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#1-install-from-the-official-selector" class="hash-link" aria-label="Direct link to 1. Install from the official selector" title="Direct link to 1. Install from the official selector" translate="no">​</a></h2>
<p>PyTorch packages depend on the operating system and accelerator runtime. Use the current choices at <a href="https://pytorch.org/get-started/locally/" target="_blank" rel="noopener noreferrer" class="">PyTorch — Start Locally</a> rather than copying a CUDA URL from an old tutorial.</p>
<p>A CPU-only pip installation is useful for learning and continuous integration:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python -m venv .venv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">source .venv/bin/activate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install --upgrade pip</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu</span><br></div></code></pre></div></div>
<p>On Windows, activate the environment with the command appropriate to PowerShell or Command Prompt. For CUDA, ROCm, XPU, or another backend, use the official selector and record the exact command.</p>
<p>Capture the environment:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python --version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m pip freeze &gt; requirements-lock.txt</span><br></div></code></pre></div></div>
<p>For a maintained project, prefer a dependency file and a deliberate lock/update process over an unreviewed snapshot.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-inspect-the-runtime-and-select-a-device">2. Inspect the runtime and select a device<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#2-inspect-the-runtime-and-select-a-device" class="hash-link" aria-label="Direct link to 2. Inspect the runtime and select a device" title="Direct link to 2. Inspect the runtime and select a device" translate="no">​</a></h2>
<p>Do not scatter unconditional <code>.cuda()</code> calls through a project. Select a device once and move both model and tensors to it.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> torch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">select_device</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"cuda"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token builtin">hasattr</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">backends</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mps"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">and</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">backends</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mps</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"mps"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"cpu"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">device </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> select_device</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"PyTorch:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">__version__</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Device:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> device</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"CUDA runtime:"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">version</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Record the device, accelerator model, driver/runtime, package versions, and precision mode with experiment results.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-set-reproducibility-controls-before-model-creation">3. Set reproducibility controls before model creation<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#3-set-reproducibility-controls-before-model-creation" class="hash-link" aria-label="Direct link to 3. Set reproducibility controls before model creation" title="Direct link to 3. Set reproducibility controls before model creation" translate="no">​</a></h2>
<p>Seeds must be set before initializing a model or shuffling data.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> os</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> random</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> numpy </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> np</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> torch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">SEED </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">42</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">os</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">environ</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"PYTHONHASHSEED"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">SEED</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">random</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">seed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">SEED</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">np</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">random</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">seed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">SEED</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">manual_seed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">SEED</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">is_available</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">cuda</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">manual_seed_all</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">SEED</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">use_deterministic_algorithms</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> warn_only</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>A seed improves repeatability but does not guarantee identical results across PyTorch versions, devices, kernels, or distributed configurations. Report tolerances and rerun important experiments.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-train-one-minimal-model">4. Train one minimal model<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#4-train-one-minimal-model" class="hash-link" aria-label="Direct link to 4. Train one minimal model" title="Direct link to 4. Train one minimal model" translate="no">​</a></h2>
<p>The XOR example demonstrates tensors, a module, logits, a loss, an optimizer, and inference without a large dataset.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> torch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> torch </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> nn</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">manual_seed</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">42</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">X </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tensor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0.0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0.0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1.0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1.0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">y </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">tensor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0.0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1.0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1.0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0.0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> nn</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Sequential</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    nn</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Linear</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    nn</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">ReLU</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    nn</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Linear</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">8</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">criterion </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> nn</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">BCEWithLogitsLoss</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">optimizer </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">optim</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Adam</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parameters</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> lr</span><span class="token operator" style="color:#393A34">=</span><span class="token number" style="color:#36acaa">0.05</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> _ </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token builtin">range</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">500</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    logits </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">X</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    loss </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> criterion</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">logits</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> y</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    optimizer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">zero_grad</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">set_to_none</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    loss</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">backward</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    optimizer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">step</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">eval</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">with</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">inference_mode</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    probabilities </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">sigmoid</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">X</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    predictions </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">probabilities </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.5</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">int32</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">probabilities</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">squeeze</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">print</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">predictions</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">squeeze</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Use <code>BCEWithLogitsLoss</code> with raw logits rather than adding a sigmoid layer before <code>BCELoss</code>. Do not publish exact expected probabilities: initialization, package versions, and numerical kernels can change them.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-structure-a-research-dataset">5. Structure a research dataset<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#5-structure-a-research-dataset" class="hash-link" aria-label="Direct link to 5. Structure a research dataset" title="Direct link to 5. Structure a research dataset" translate="no">​</a></h2>
<p>Keep dataset splitting independent of preprocessing fitted on the data.</p>
<ol>
<li class="">define the observational unit;</li>
<li class="">split by a unit that prevents leakage, such as plant, plot, field, date, or subject;</li>
<li class="">fit normalization and feature transforms on the training split only;</li>
<li class="">freeze the validation split for model selection;</li>
<li class="">touch the test split only for final evaluation.</li>
</ol>
<p>For image phenotyping, random image-level splitting can leak near-duplicate views of the same plant into training and validation. Group-aware splitting is usually more defensible.</p>
<p>A custom dataset should return a sample and target without silently changing global state:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> pathlib </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Path</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> PIL </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Image</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">utils</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">data </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Dataset</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">ImageTableDataset</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">Dataset</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">__init__</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> rows</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> transform</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rows </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">list</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">rows</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">transform </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> transform</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">__len__</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">len</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rows</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">__getitem__</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">self</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> index</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        row </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rows</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">index</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        image </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Image</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">open</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">Path</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"path"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">convert</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"RGB"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">transform </span><span class="token keyword" style="color:#00009f">is</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">not</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">None</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            image </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">transform</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">image</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> image</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">int</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"label"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Validate paths and labels when constructing <code>rows</code>, and document how missing or corrupt samples are handled.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-use-a-transparent-training-loop">6. Use a transparent training loop<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#6-use-a-transparent-training-loop" class="hash-link" aria-label="Direct link to 6. Use a transparent training loop" title="Direct link to 6. Use a transparent training loop" translate="no">​</a></h2>
<p>One epoch should have a clear contract: consume a loader, update the model, and return a sample-weighted metric.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">train_one_epoch</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> loader</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> criterion</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> optimizer</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> device</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">train</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    loss_sum </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    sample_count </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> inputs</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> targets </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> loader</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        inputs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> inputs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        targets </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> targets</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        optimizer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">zero_grad</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">set_to_none</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        outputs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">inputs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        loss </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> criterion</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">outputs</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> targets</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        loss</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">backward</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        optimizer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">step</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        batch_size </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> inputs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">shape</span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        loss_sum </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> loss</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">detach</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">item</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">*</span><span class="token plain"> batch_size</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        sample_count </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> batch_size</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> loss_sum </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> sample_count</span><br></div></code></pre></div></div>
<p>For validation:</p>
<ul>
<li class="">call <code>model.eval()</code>;</li>
<li class="">wrap inference in <code>torch.inference_mode()</code>;</li>
<li class="">never update model parameters;</li>
<li class="">aggregate metrics over all samples;</li>
<li class="">store predictions and identifiers when error analysis is needed.</li>
</ul>
<p>Avoid selecting a model from test-set performance.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-add-mixed-precision-only-when-supported">7. Add mixed precision only when supported<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#7-add-mixed-precision-only-when-supported" class="hash-link" aria-label="Direct link to 7. Add mixed precision only when supported" title="Direct link to 7. Add mixed precision only when supported" translate="no">​</a></h2>
<p>Automatic mixed precision can improve CUDA throughput, but it is an optimization, not a correctness requirement. Establish a full-precision baseline first.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> contextlib </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> nullcontext</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> torch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">use_amp </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> device</span><span class="token punctuation" style="color:#393A34">.</span><span class="token builtin">type</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">==</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cuda"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">scaler </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">amp</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">GradScaler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"cuda"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> enabled</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">use_amp</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> inputs</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> targets </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> train_loader</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    inputs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> inputs</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    targets </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> targets</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">device</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    optimizer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">zero_grad</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">set_to_none</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    precision_context </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">autocast</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">device_type</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"cuda"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> dtype</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">float16</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> use_amp</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">else</span><span class="token plain"> nullcontext</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">with</span><span class="token plain"> precision_context</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        outputs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">inputs</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        loss </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> criterion</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">outputs</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> targets</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    scaler</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">scale</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">loss</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">backward</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    scaler</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">step</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">optimizer</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    scaler</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">update</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>The old <code>torch.cuda.amp.autocast</code> and <code>torch.cuda.amp.GradScaler</code> namespaces are deprecated in current documentation. Monitor loss, gradients, and metrics for NaN or overflow when changing precision.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="8-start-computer-vision-with-a-maintained-baseline">8. Start computer vision with a maintained baseline<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#8-start-computer-vision-with-a-maintained-baseline" class="hash-link" aria-label="Direct link to 8. Start computer vision with a maintained baseline" title="Direct link to 8. Start computer vision with a maintained baseline" translate="no">​</a></h2>
<p>Torchvision weight enums couple pretrained parameters to documented preprocessing:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> torch </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> nn</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> torchvision</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">models </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> ResNet18_Weights</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> resnet18</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">weights </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ResNet18_Weights</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">DEFAULT</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">preprocess </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> weights</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">transforms</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">model </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> resnet18</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">weights</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">weights</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">fc </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> nn</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Linear</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">fc</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">in_features</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">4</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Record the weights enum, input resolution, transform, class mapping, and fine-tuning policy. Avoid calling a single output head a complete YOLO detector: object detection also requires target encoding, a loss, decoding, non-maximum suppression, evaluation, and task-specific training.</p>
<p>For plant images, compare a learned model against simple baselines and evaluate across the domains that matter—cultivar, growth stage, sensor, field, date, and lighting.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="9-save-state-safely">9. Save state safely<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#9-save-state-safely" class="hash-link" aria-label="Direct link to 9. Save state safely" title="Direct link to 9. Save state safely" translate="no">​</a></h2>
<p>Save state dictionaries and metadata rather than serializing an arbitrary live Python object:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> pathlib </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Path</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> torch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">checkpoint_path </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> Path</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"checkpoints/model.pt"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">checkpoint_path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">parent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">mkdir</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">parents</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> exist_ok</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">save</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"model_state_dict"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">state_dict</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"optimizer_state_dict"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> optimizer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">state_dict</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"epoch"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> epoch</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"class_names"</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> class_names</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    checkpoint_path</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Load only trusted files and map them to a known device:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">checkpoint </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">load</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"checkpoints/model.pt"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    map_location</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"cpu"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    weights_only</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">load_state_dict</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">checkpoint</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"model_state_dict"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p><code>torch.load</code> uses restricted loading with <code>weights_only=True</code>, but a project should still treat external model artifacts as untrusted input and verify their provenance and integrity.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="10-evaluate-more-than-one-number">10. Evaluate more than one number<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#10-evaluate-more-than-one-number" class="hash-link" aria-label="Direct link to 10. Evaluate more than one number" title="Direct link to 10. Evaluate more than one number" translate="no">​</a></h2>
<p>Choose metrics before looking at final results.</p>
<ul>
<li class=""><strong>Classification:</strong> confusion matrix, per-class precision/recall, macro F1, calibration, and uncertainty intervals</li>
<li class=""><strong>Regression:</strong> MAE, RMSE, bias, residual plots, and errors by biological or acquisition subgroup</li>
<li class=""><strong>Segmentation:</strong> IoU/Dice per class, boundary error, object-level errors, and failure cases</li>
<li class=""><strong>Detection:</strong> the metric definition, IoU range, object-size strata, and precision–recall curves</li>
</ul>
<p>Report dataset composition and uncertainty. A high aggregate score can hide failure on a cultivar, field, camera, or rare class.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="11-treat-deployment-as-a-separate-engineering-phase">11. Treat deployment as a separate engineering phase<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#11-treat-deployment-as-a-separate-engineering-phase" class="hash-link" aria-label="Direct link to 11. Treat deployment as a separate engineering phase" title="Direct link to 11. Treat deployment as a separate engineering phase" translate="no">​</a></h2>
<p>A notebook inference call is not a production service. Before deployment, define:</p>
<ul>
<li class="">model and preprocessing version;</li>
<li class="">input schema, size, and content limits;</li>
<li class="">authentication and authorization;</li>
<li class="">request rate and resource limits;</li>
<li class="">timeout, batching, and concurrency behavior;</li>
<li class="">privacy and data-retention policy;</li>
<li class="">observability, rollback, and drift monitoring;</li>
<li class="">tests using the exported artifact, not only the training model.</li>
</ul>
<p>ONNX, <code>torch.export</code>, TorchScript, accelerator compilers, and serving frameworks have version-specific constraints. Select one only after measuring it on the target hardware. A minimal Flask endpoint without these controls should be described as a local demonstration, not production deployment.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="troubleshooting-checklist">Troubleshooting checklist<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#troubleshooting-checklist" class="hash-link" aria-label="Direct link to Troubleshooting checklist" title="Direct link to Troubleshooting checklist" translate="no">​</a></h2>
<table><thead><tr><th>Symptom</th><th>First checks</th></tr></thead><tbody><tr><td>Out of memory</td><td>Input size, batch size, retained computation graphs, precision, and unused tensors</td></tr><tr><td>Device mismatch</td><td>Model, inputs, targets, and newly created tensors use the same device</td></tr><tr><td>DataLoader hangs</td><td>Start with <code>num_workers=0</code>, then increase while testing the platform</td></tr><tr><td>Loss is NaN</td><td>Input ranges, labels, learning rate, loss assumptions, precision, and gradients</td></tr><tr><td>Results change</td><td>Seeds, data ordering, augmentation, package versions, kernels, and split leakage</td></tr><tr><td>Checkpoint fails</td><td>Architecture and class mapping match; load a trusted state dictionary with an explicit device</td></tr></tbody></table>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="suggested-learning-sequence">Suggested learning sequence<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#suggested-learning-sequence" class="hash-link" aria-label="Direct link to Suggested learning sequence" title="Direct link to Suggested learning sequence" translate="no">​</a></h2>
<ol>
<li class="">tensors, shapes, dtypes, and autograd;</li>
<li class=""><code>Dataset</code>, <code>DataLoader</code>, and leakage-safe splitting;</li>
<li class="">one transparent training and validation loop;</li>
<li class="">a simple baseline and documented metrics;</li>
<li class="">transfer learning with versioned preprocessing;</li>
<li class="">experiment tracking and ablation studies;</li>
<li class="">export and deployment validation.</li>
</ol>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="official-resources">Official resources<a href="https://smiler488.com/blog/pytorch-ml-dl-tutorial#official-resources" class="hash-link" aria-label="Direct link to Official resources" title="Direct link to Official resources" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://pytorch.org/docs/" target="_blank" rel="noopener noreferrer" class="">PyTorch documentation</a></li>
<li class=""><a href="https://pytorch.org/tutorials/" target="_blank" rel="noopener noreferrer" class="">PyTorch tutorials</a></li>
<li class=""><a href="https://discuss.pytorch.org/" target="_blank" rel="noopener noreferrer" class="">PyTorch Forums</a></li>
<li class=""><a href="https://github.com/pytorch/pytorch/issues" target="_blank" rel="noopener noreferrer" class="">PyTorch GitHub issues</a></li>
</ul>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Machine Learning" term="Machine Learning"/>
        <category label="Artificial Intelligence" term="Artificial Intelligence"/>
        <category label="Python" term="Python"/>
        <category label="Computer Vision" term="Computer Vision"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Turntable Photogrammetry for Potted Cotton in a Growth Chamber]]></title>
        <id>https://smiler488.com/blog/growth-chamber-cotton-3d</id>
        <link href="https://smiler488.com/blog/growth-chamber-cotton-3d"/>
        <updated>2022-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[An experimental, validation-first protocol for acquiring and reconstructing multi-view images of potted cotton plants in a controlled environment.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="project-overview">Project overview<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#project-overview" class="hash-link" aria-label="Direct link to Project overview" title="Direct link to Project overview" translate="no">​</a></h2>
<p>This protocol uses a rotating plant and fixed cameras to create a multi-view image set for 3D reconstruction. It is intended as an experimental starting point, not a validated claim of high-precision phenotyping. Accuracy depends on plant motion, image sharpness, calibration, scale control, background masking, and independent validation.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="research-objective">Research objective<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#research-objective" class="hash-link" aria-label="Direct link to Research objective" title="Direct link to Research objective" translate="no">​</a></h2>
<p>The workflow can support:</p>
<ul>
<li class="">visualization of plant architecture;</li>
<li class="">exploratory estimates of plant height, width, volume, and leaf orientation;</li>
<li class="">development of organ-segmentation and light-distribution methods;</li>
<li class="">comparison of reconstruction settings under controlled acquisition.</li>
</ul>
<p>Do not treat mesh-derived traits as ground truth until their errors have been quantified against independent measurements.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-prepare-the-imaging-area">1. Prepare the imaging area<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#1-prepare-the-imaging-area" class="hash-link" aria-label="Direct link to 1. Prepare the imaging area" title="Direct link to 1. Prepare the imaging area" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="stable-environment">Stable environment<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#stable-environment" class="hash-link" aria-label="Direct link to Stable environment" title="Direct link to Stable environment" translate="no">​</a></h3>
<ul>
<li class="">Stop fans and minimize airflow during capture; small leaf motion can break feature matching.</li>
<li class="">Use diffuse, flicker-free light and keep it constant for the full sequence.</li>
<li class="">Measure and record illuminance or exposure conditions rather than adopting an arbitrary universal lux value.</li>
<li class="">Avoid specular highlights, deep shadows, and automatic lighting changes.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="background-and-turntable">Background and turntable<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#background-and-turntable" class="hash-link" aria-label="Direct link to Background and turntable" title="Direct link to Background and turntable" translate="no">​</a></h3>
<ul>
<li class="">Use a matte, visually uniform background that can be masked reliably.</li>
<li class="">Use a rigid, matte turntable large enough for the pot and plant.</li>
<li class="">Avoid transparent or reflective acrylic unless its reflections are controlled and validated.</li>
<li class="">Keep static background features, color charts, cables, and supports out of the reconstruction mask. In turntable photogrammetry, the plant moves relative to the room, so static background features violate the assumed scene geometry.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="scale-and-control">Scale and control<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#scale-and-control" class="hash-link" aria-label="Direct link to Scale and control" title="Direct link to Scale and control" translate="no">​</a></h3>
<p>Place measured scale bars and uniquely identifiable coded markers on the rotating platform so they move with the plant. Avoid symmetric, repeated marker layouts that can create ambiguous correspondences.</p>
<p>Reserve at least one independent scale or distance as a check rather than using every measurement to define the model.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-configure-the-cameras">2. Configure the cameras<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#2-configure-the-cameras" class="hash-link" aria-label="Direct link to 2. Configure the cameras" title="Direct link to 2. Configure the cameras" translate="no">​</a></h2>
<p>One well-controlled camera moved between height levels is often easier to calibrate than two unmatched phone cameras. If multiple cameras are used:</p>
<ul>
<li class="">lock focus, shutter speed, ISO, white balance, and focal length;</li>
<li class="">disable automatic HDR or lens switching when possible;</li>
<li class="">record each device, lens, resolution, frame rate, and exposure setting;</li>
<li class="">acquire calibration data for each camera;</li>
<li class="">avoid digital zoom;</li>
<li class="">verify that all views are sharp and free from rolling-shutter or stabilization artifacts.</li>
</ul>
<p>Phone video is compressed and may apply computational processing between frames. Still images or high-quality intra-frame video are preferable when the workflow permits.</p>
<p>Use at least two elevation bands to reduce top- and underside occlusion. Exact angles and distances depend on plant size and field of view; verify that the entire specimen and scale controls remain visible.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-capture-the-sequence">3. Capture the sequence<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#3-capture-the-sequence" class="hash-link" aria-label="Direct link to 3. Capture the sequence" title="Direct link to 3. Capture the sequence" translate="no">​</a></h2>
<ol>
<li class="">Center and secure the pot without deforming the plant.</li>
<li class="">Record a sharp reference view and the experiment metadata.</li>
<li class="">Start a slow, constant rotation.</li>
<li class="">Capture enough angular views to maintain feature overlap around the full plant.</li>
<li class="">Repeat at the second camera height or elevation.</li>
<li class="">Inspect the sequence immediately for blur, exposure drift, leaf motion, missing regions, and marker visibility.</li>
</ol>
<p>Do not export every one or two video frames by default. At 30 frames per second, that produces thousands of highly redundant images. Sample by angular coverage and image quality. Document the final angular interval, number of retained frames, and rejection criteria.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-manage-color-separately-from-geometry">4. Manage color separately from geometry<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#4-manage-color-separately-from-geometry" class="hash-link" aria-label="Direct link to 4. Manage color separately from geometry" title="Direct link to 4. Manage color separately from geometry" translate="no">​</a></h2>
<p>A color chart such as SpyderCheck24 can help monitor camera and lighting consistency, but it does not by itself make image values physically calibrated reflectance.</p>
<ul>
<li class="">Capture the chart under the same camera and lighting settings.</li>
<li class="">Apply one documented correction consistently to the sequence.</li>
<li class="">Keep the static chart out of the geometry reconstruction mask.</li>
<li class="">Preserve the uncorrected source files and the correction parameters.</li>
</ul>
<p>If color is a scientific output, validate the corrected patch values and report the color space, white balance, exposure, and error metric.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-organize-and-screen-the-images">5. Organize and screen the images<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#5-organize-and-screen-the-images" class="hash-link" aria-label="Direct link to 5. Organize and screen the images" title="Direct link to 5. Organize and screen the images" translate="no">​</a></h2>
<p>Use names that preserve plant, camera, elevation, and view order:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Plant01/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── raw/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── camera-a/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── camera-b/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── selected/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── upper/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── horizontal/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── masks/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── calibration/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── reconstruction/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└── validation/</span><br></div></code></pre></div></div>
<p>For every selected image, check:</p>
<ul>
<li class="">sharpness at leaf edges;</li>
<li class="">consistent exposure and white balance;</li>
<li class="">sufficient overlap with adjacent views;</li>
<li class="">no large leaf displacement;</li>
<li class="">no accidental crop of the plant or scale controls;</li>
<li class="">a mask that excludes the static room and color chart.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-reconstruct-in-metashape-or-comparable-software">6. Reconstruct in Metashape or comparable software<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#6-reconstruct-in-metashape-or-comparable-software" class="hash-link" aria-label="Direct link to 6. Reconstruct in Metashape or comparable software" title="Direct link to 6. Reconstruct in Metashape or comparable software" translate="no">​</a></h2>
<p>Software labels vary by version, so the workflow is described by purpose:</p>
<ol>
<li class="">import the selected images;</li>
<li class="">apply masks before or during feature matching;</li>
<li class="">estimate camera poses and a sparse reconstruction;</li>
<li class="">inspect and remove obvious outlier tie points cautiously;</li>
<li class="">identify coded markers and enter measured scale constraints;</li>
<li class="">optimize camera parameters only after checking that the control geometry is correct;</li>
<li class="">generate depth maps and a dense point cloud;</li>
<li class="">remove unsupported background geometry;</li>
<li class="">build and, if required, texture a mesh;</li>
<li class="">export the point cloud or mesh with units and coordinate metadata.</li>
</ol>
<p>Do not copy key-point limits, depth settings, or filtering strengths as universal defaults. Record the software version and run a small parameter comparison on representative plants.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-validate-before-extracting-traits">7. Validate before extracting traits<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#7-validate-before-extracting-traits" class="hash-link" aria-label="Direct link to 7. Validate before extracting traits" title="Direct link to 7. Validate before extracting traits" translate="no">​</a></h2>
<p>At minimum, report:</p>
<ul>
<li class="">number of input and aligned images;</li>
<li class="">camera reprojection error, with its definition and units;</li>
<li class="">marker and scale residuals;</li>
<li class="">error on an independent distance or object;</li>
<li class="">visibly missing or falsely filled plant regions;</li>
<li class="">repeatability across replicate captures;</li>
<li class="">sensitivity of traits to masking and reconstruction settings.</li>
</ul>
<p>For manual plant height or width measurements (m_i) and reconstructed values (r_i), summarize bias and RMSE:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">bias = mean(r_i - m_i)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">RMSE = sqrt(mean((r_i - m_i)^2))</span><br></div></code></pre></div></div>
<p>A visually plausible mesh can still give biased traits, particularly for thin leaf margins, overlapping leaves, and reflective or texture-poor surfaces.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="8-export-research-ready-outputs">8. Export research-ready outputs<a href="https://smiler488.com/blog/growth-chamber-cotton-3d#8-export-research-ready-outputs" class="hash-link" aria-label="Direct link to 8. Export research-ready outputs" title="Direct link to 8. Export research-ready outputs" translate="no">​</a></h2>
<p>Preserve:</p>
<ul>
<li class="">original images and metadata;</li>
<li class="">selected-frame manifest and rejection reasons;</li>
<li class="">masks and calibration records;</li>
<li class="">reconstruction project and software version;</li>
<li class="">scale constraints and independent validation measurements;</li>
<li class="">exported PLY/OBJ/GLB with units;</li>
<li class="">scripts, parameters, and trait tables linked to a code commit.</li>
</ul>
<p>This evidence makes the reconstruction auditable and allows future processing improvements without repeating the experiment.</p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Plant Phenotyping" term="Plant Phenotyping"/>
        <category label="3D Reconstruction" term="3D Reconstruction"/>
        <category label="Image Analysis" term="Image Analysis"/>
        <category label="Computer Vision" term="Computer Vision"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[A Reproducible Research Workflow with VS Code, Miniconda, and Git]]></title>
        <id>https://smiler488.com/blog/workflow-vscode-miniconda-git</id>
        <link href="https://smiler488.com/blog/workflow-vscode-miniconda-git"/>
        <updated>2022-02-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A practical workflow for structuring Python research projects, recording environments, collaborating through Git, and preserving data provenance.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="project-overview">Project overview<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#project-overview" class="hash-link" aria-label="Direct link to Project overview" title="Direct link to Project overview" translate="no">​</a></h2>
<p>This workflow combines VS Code, a project-specific Conda environment, and Git. The tools are useful, but reproducibility comes from the records around them: environment specifications, immutable raw data, configuration files, checksums, clear commits, and documented outputs.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-install-the-core-tools">1. Install the core tools<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#1-install-the-core-tools" class="hash-link" aria-label="Direct link to 1. Install the core tools" title="Direct link to 1. Install the core tools" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://code.visualstudio.com/" target="_blank" rel="noopener noreferrer" class="">Visual Studio Code</a> with the Python, Pylance, and Jupyter extensions</li>
<li class=""><a href="https://docs.conda.io/en/latest/miniconda.html" target="_blank" rel="noopener noreferrer" class="">Miniconda</a></li>
<li class=""><a href="https://git-scm.com/downloads" target="_blank" rel="noopener noreferrer" class="">Git</a></li>
</ul>
<p>Confirm that the active shell can find them:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">code --version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">conda --version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git --version</span><br></div></code></pre></div></div>
<p>Configure Git once:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git config --global user.name "Your Name"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git config --global user.email "you@example.com"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git config --global init.defaultBranch main</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-create-a-project-and-environment">2. Create a project and environment<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#2-create-a-project-and-environment" class="hash-link" aria-label="Direct link to 2. Create a project and environment" title="Direct link to 2. Create a project and environment" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">mkdir cotton-modeling</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd cotton-modeling</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git init</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">conda create --name cotton python=3.11</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">conda activate cotton</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">conda install numpy pandas matplotlib scikit-learn</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">conda install --channel conda-forge opencv open3d</span><br></div></code></pre></div></div>
<p>Choose the Python version and packages required by the project rather than copying this example unchanged.</p>
<p>Open the folder:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">code .</span><br></div></code></pre></div></div>
<p>In VS Code, run <strong>Python: Select Interpreter</strong> and select the <code>cotton</code> environment.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-use-a-research-friendly-structure">3. Use a research-friendly structure<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#3-use-a-research-friendly-structure" class="hash-link" aria-label="Direct link to 3. Use a research-friendly structure" title="Direct link to 3. Use a research-friendly structure" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">cotton-modeling/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── README.md</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── environment.yml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── pyproject.toml          # optional package and tool configuration</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── configs/                # versioned experiment parameters</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── data/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── README.md           # source, license, schema, and retrieval notes</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── raw/                # immutable source data</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── processed/          # reproducible derived data</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── notebooks/              # exploration, not the only implementation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── src/cotton_modeling/    # reusable Python modules</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── tests/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── results/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── README.md           # explains how outputs are generated</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└── .gitignore</span><br></div></code></pre></div></div>
<p>Keep raw data immutable. A processing script should create a new derived artifact rather than overwrite its input.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-decide-what-git-should-track">4. Decide what Git should track<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#4-decide-what-git-should-track" class="hash-link" aria-label="Direct link to 4. Decide what Git should track" title="Direct link to 4. Decide what Git should track" translate="no">​</a></h2>
<p>A starting <code>.gitignore</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">__pycache__/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">*.py[cod]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">.ipynb_checkpoints/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">.env</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">.DS_Store</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">data/raw/*</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">data/processed/*</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">results/generated/*</span><br></div></code></pre></div></div>
<p>Do not ignore the explanatory <code>README.md</code> files, schemas, small test fixtures, configuration, or the code required to reproduce an output.</p>
<p>For data too large or restricted for Git:</p>
<ul>
<li class="">store it in an approved repository or object store;</li>
<li class="">record a persistent identifier or retrieval location;</li>
<li class="">record checksums and access dates;</li>
<li class="">use DVC or Git LFS only when they fit the collaboration and preservation plan.</li>
</ul>
<p>Never commit credentials from <code>.env</code>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-record-the-environment">5. Record the environment<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#5-record-the-environment" class="hash-link" aria-label="Direct link to 5. Record the environment" title="Direct link to 5. Record the environment" translate="no">​</a></h2>
<p>For a portable list of the packages you explicitly requested:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">conda env export --from-history &gt; environment.yml</span><br></div></code></pre></div></div>
<p>Recreate it with:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">conda env create --file environment.yml</span><br></div></code></pre></div></div>
<p><code>--from-history</code> is readable and cross-platform, but it does not lock every transitive dependency. When exact package builds matter, create a platform-specific explicit specification or use a lock-file tool, then archive that file with the release.</p>
<p>Update the environment description only after confirming the project still runs:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">conda env export --from-history &gt; environment.yml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git diff environment.yml</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-keep-notebooks-and-modules-in-sync">6. Keep notebooks and modules in sync<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#6-keep-notebooks-and-modules-in-sync" class="hash-link" aria-label="Direct link to 6. Keep notebooks and modules in sync" title="Direct link to 6. Keep notebooks and modules in sync" translate="no">​</a></h2>
<p>Use notebooks for exploration and communication, but move stable operations into <code>src/</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># src/cotton_modeling/preprocessing.py</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> pathlib </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> Path</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">list_images</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">folder</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">list</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    extensions </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">".jpg"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".jpeg"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".png"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".tif"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">".tiff"</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token builtin">sorted</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        path </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> path </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> Path</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">folder</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">iterdir</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">suffix</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">lower</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> extensions</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Notebooks should import these functions instead of containing the only copy of an analysis.</p>
<p>For a named Jupyter kernel:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">conda install ipykernel</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m ipykernel install --user --name cotton --display-name "Python (cotton)"</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-commit-a-reproducible-unit-of-work">7. Commit a reproducible unit of work<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#7-commit-a-reproducible-unit-of-work" class="hash-link" aria-label="Direct link to 7. Commit a reproducible unit of work" title="Direct link to 7. Commit a reproducible unit of work" translate="no">​</a></h2>
<p>Review before staging:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git status</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git diff</span><br></div></code></pre></div></div>
<p>Commit code, configuration, tests, and documentation together:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git add README.md environment.yml configs src tests</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git diff --staged</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "feat: add canopy preprocessing pipeline"</span><br></div></code></pre></div></div>
<p>Connect a GitHub repository:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git branch -M main</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git remote add origin https://github.com/yourname/cotton_modeling.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push -u origin main</span><br></div></code></pre></div></div>
<p>Before starting new work:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git fetch origin</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git status</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git pull --ff-only origin main</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="8-choose-one-collaboration-model">8. Choose one collaboration model<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#8-choose-one-collaboration-model" class="hash-link" aria-label="Direct link to 8. Choose one collaboration model" title="Direct link to 8. Choose one collaboration model" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="shared-repository">Shared repository<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#shared-repository" class="hash-link" aria-label="Direct link to Shared repository" title="Direct link to Shared repository" translate="no">​</a></h3>
<p>Members with write access create branches in the same repository:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git switch -c feature-light-simulation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># edit and test</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git add configs src tests</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "feat: add light simulation module"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push -u origin feature-light-simulation</span><br></div></code></pre></div></div>
<p>Open a pull request, review the diff and checks, then merge.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="fork-based-contribution">Fork-based contribution<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#fork-based-contribution" class="hash-link" aria-label="Direct link to Fork-based contribution" title="Direct link to Fork-based contribution" translate="no">​</a></h3>
<p>Contributors without write access clone their own fork and register the original repository as <code>upstream</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone https://github.com/yourname/cotton_modeling.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd cotton_modeling</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git remote add upstream https://github.com/leader/cotton_modeling.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git fetch upstream</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git switch -c analysis-update</span><br></div></code></pre></div></div>
<p>Push the branch to the fork and open a pull request against <code>upstream/main</code>.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="9-reproducibility-checklist">9. Reproducibility checklist<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#9-reproducibility-checklist" class="hash-link" aria-label="Direct link to 9. Reproducibility checklist" title="Direct link to 9. Reproducibility checklist" translate="no">​</a></h2>
<p>For each analysis or model run, preserve:</p>
<ul>
<li class="">code commit and, for releases, an annotated Git tag;</li>
<li class="">environment or lock file;</li>
<li class="">input dataset identifier, version, license, and checksum;</li>
<li class="">configuration and random seeds;</li>
<li class="">hardware or accelerator details when results are sensitive to them;</li>
<li class="">commands used to run the workflow;</li>
<li class="">generated logs, metrics, and a description of expected outputs;</li>
<li class="">any manual step that cannot yet be automated.</li>
</ul>
<p>Do not claim bit-for-bit reproducibility across platforms unless it has been tested. The stronger and usually more useful target is a documented workflow that reproduces the scientific conclusion within defined tolerances.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="common-issues">Common issues<a href="https://smiler488.com/blog/workflow-vscode-miniconda-git#common-issues" class="hash-link" aria-label="Direct link to Common issues" title="Direct link to Common issues" translate="no">​</a></h2>
<table><thead><tr><th>Issue</th><th>Check</th></tr></thead><tbody><tr><td>VS Code uses the wrong Python</td><td>Run <strong>Python: Select Interpreter</strong> and verify <code>python -c "import sys; print(sys.executable)"</code>.</td></tr><tr><td>Notebook kernel is missing</td><td>Install <code>ipykernel</code> inside the environment and register the kernel.</td></tr><tr><td>Conda cannot solve dependencies</td><td>Remove unnecessary constraints, create a fresh environment, and document the resolved set.</td></tr><tr><td>Push authentication fails</td><td>Use GitHub CLI, a credential manager, PAT, or SSH; account passwords are not accepted for Git pushes.</td></tr><tr><td>A large dataset was committed</td><td>Stop, review whether history was published, and follow the repository's approved data-removal process.</td></tr></tbody></table>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Reproducible Research" term="Reproducible Research"/>
        <category label="Python" term="Python"/>
        <category label="Git" term="Git"/>
        <category label="Data Analysis" term="Data Analysis"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Git and GitHub Beginner Guide]]></title>
        <id>https://smiler488.com/blog/gitHub-beginner-guide</id>
        <link href="https://smiler488.com/blog/gitHub-beginner-guide"/>
        <updated>2021-04-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A safe, modern introduction to repositories, commits, branches, remotes, pull requests, authentication, and undoing mistakes.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="project-overview">Project overview<a href="https://smiler488.com/blog/gitHub-beginner-guide#project-overview" class="hash-link" aria-label="Direct link to Project overview" title="Direct link to Project overview" translate="no">​</a></h2>
<p>Git records changes to files on your computer. GitHub hosts Git repositories and adds collaboration features such as pull requests, issues, and automated checks. This guide follows a complete first workflow and separates safe recovery commands from history-rewriting operations.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-install-and-identify-yourself">1. Install and identify yourself<a href="https://smiler488.com/blog/gitHub-beginner-guide#1-install-and-identify-yourself" class="hash-link" aria-label="Direct link to 1. Install and identify yourself" title="Direct link to 1. Install and identify yourself" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="install-git">Install Git<a href="https://smiler488.com/blog/gitHub-beginner-guide#install-git" class="hash-link" aria-label="Direct link to Install Git" title="Direct link to Install Git" translate="no">​</a></h3>
<ul>
<li class="">
<p><strong>Windows:</strong> download <a href="https://git-scm.com/" target="_blank" rel="noopener noreferrer" class="">Git for Windows</a>.</p>
</li>
<li class="">
<p><strong>macOS:</strong> install the Xcode Command Line Tools or use Homebrew:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">brew install git</span><br></div></code></pre></div></div>
</li>
<li class="">
<p><strong>Ubuntu or Debian:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">sudo apt update</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sudo apt install git</span><br></div></code></pre></div></div>
</li>
</ul>
<p>Confirm the installation:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git --version</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="configure-commit-identity">Configure commit identity<a href="https://smiler488.com/blog/gitHub-beginner-guide#configure-commit-identity" class="hash-link" aria-label="Direct link to Configure commit identity" title="Direct link to Configure commit identity" translate="no">​</a></h3>
<p>Use the name you want shown in commit history. The email should be an address associated with your GitHub account, or your GitHub-provided private <code>noreply</code> address.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git config --global user.name "Your Name"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git config --global user.email "you@example.com"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git config --global init.defaultBranch main</span><br></div></code></pre></div></div>
<p>Review the configuration:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git config --global --list</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-create-a-repository">2. Create a repository<a href="https://smiler488.com/blog/gitHub-beginner-guide#2-create-a-repository" class="hash-link" aria-label="Direct link to 2. Create a repository" title="Direct link to 2. Create a repository" translate="no">​</a></h2>
<p>Create a folder locally:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">mkdir my-project</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd my-project</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git init</span><br></div></code></pre></div></div>
<p>Add a minimal project description:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">echo "# My Project" &gt; README.md</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git status</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git add README.md</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "docs: add project overview"</span><br></div></code></pre></div></div>
<p>Before staging data, credentials, or generated files, create a <code>.gitignore</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">.env</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">node_modules/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">__pycache__/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">*.log</span><br></div></code></pre></div></div>
<p>Never commit API keys or passwords. Removing a secret in a later commit does not remove it from earlier history; rotate an exposed credential immediately.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-connect-the-github-remote">3. Connect the GitHub remote<a href="https://smiler488.com/blog/gitHub-beginner-guide#3-connect-the-github-remote" class="hash-link" aria-label="Direct link to 3. Connect the GitHub remote" title="Direct link to 3. Connect the GitHub remote" translate="no">​</a></h2>
<p>Create an empty repository on <a href="https://github.com/" target="_blank" rel="noopener noreferrer" class="">GitHub</a> without initializing another README, then connect it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git branch -M main</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git remote add origin https://github.com/your-username/repository-name.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git remote -v</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push -u origin main</span><br></div></code></pre></div></div>
<p>GitHub does not accept an account password for Git operations over HTTPS. Use a supported credential manager, GitHub CLI, a personal access token, or SSH authentication.</p>
<p>For an existing repository:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone https://github.com/your-username/repository-name.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd repository-name</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-the-everyday-edit-cycle">4. The everyday edit cycle<a href="https://smiler488.com/blog/gitHub-beginner-guide#4-the-everyday-edit-cycle" class="hash-link" aria-label="Direct link to 4. The everyday edit cycle" title="Direct link to 4. The everyday edit cycle" translate="no">​</a></h2>
<p>Inspect changes before staging:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git status</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git diff</span><br></div></code></pre></div></div>
<p>Stage intentionally and commit a coherent unit:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git add path/to/file</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git diff --staged</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "feat: describe the change"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push</span><br></div></code></pre></div></div>
<p>Prefer specific paths over <code>git add .</code> when a workspace contains unrelated or generated changes.</p>
<p>Before starting new work on a shared branch:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git fetch origin</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git status</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git pull --ff-only origin main</span><br></div></code></pre></div></div>
<p><code>--ff-only</code> refuses to create an unexpected merge commit. If local and remote histories have diverged, inspect them and decide whether to rebase or merge rather than forcing the operation.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-work-on-a-branch">5. Work on a branch<a href="https://smiler488.com/blog/gitHub-beginner-guide#5-work-on-a-branch" class="hash-link" aria-label="Direct link to 5. Work on a branch" title="Direct link to 5. Work on a branch" translate="no">​</a></h2>
<p>Create and switch to a feature branch:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git switch -c feature-clear-name</span><br></div></code></pre></div></div>
<p>Commit and publish it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git add path/to/file</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "feat: add clear capability"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push -u origin feature-clear-name</span><br></div></code></pre></div></div>
<p>After review, return to the default branch and update it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git switch main</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git pull --ff-only origin main</span><br></div></code></pre></div></div>
<p>Delete a fully merged local branch:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git branch -d feature-clear-name</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-forks-and-pull-requests">6. Forks and pull requests<a href="https://smiler488.com/blog/gitHub-beginner-guide#6-forks-and-pull-requests" class="hash-link" aria-label="Direct link to 6. Forks and pull requests" title="Direct link to 6. Forks and pull requests" translate="no">​</a></h2>
<p>Use a fork when you do not have permission to push branches to the upstream repository.</p>
<ol>
<li class="">
<p>Open the upstream repository and select <strong>Fork</strong>.</p>
</li>
<li class="">
<p>Clone your fork, not the original repository:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone https://github.com/your-username/forked-repository.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd forked-repository</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Add the original repository as <code>upstream</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git remote add upstream https://github.com/original-owner/repository.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git fetch upstream</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Create a branch, commit, and push it to your fork:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git switch -c analysis-update</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git add path/to/file</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "feat: update analysis"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push -u origin analysis-update</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>On GitHub, open a pull request from the fork branch to the upstream default branch.</p>
</li>
</ol>
<p>To synchronize later:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git switch main</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git fetch upstream</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git merge --ff-only upstream/main</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push origin main</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-undo-changes-safely">7. Undo changes safely<a href="https://smiler488.com/blog/gitHub-beginner-guide#7-undo-changes-safely" class="hash-link" aria-label="Direct link to 7. Undo changes safely" title="Direct link to 7. Undo changes safely" translate="no">​</a></h2>
<p>Choose the command based on where the mistake exists.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="discard-an-unstaged-file-edit">Discard an unstaged file edit<a href="https://smiler488.com/blog/gitHub-beginner-guide#discard-an-unstaged-file-edit" class="hash-link" aria-label="Direct link to Discard an unstaged file edit" title="Direct link to Discard an unstaged file edit" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git restore path/to/file</span><br></div></code></pre></div></div>
<p>This permanently replaces the working copy with the last committed version.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="unstage-a-file-but-keep-its-edits">Unstage a file but keep its edits<a href="https://smiler488.com/blog/gitHub-beginner-guide#unstage-a-file-but-keep-its-edits" class="hash-link" aria-label="Direct link to Unstage a file but keep its edits" title="Direct link to Unstage a file but keep its edits" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git restore --staged path/to/file</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="correct-the-most-recent-local-commit">Correct the most recent local commit<a href="https://smiler488.com/blog/gitHub-beginner-guide#correct-the-most-recent-local-commit" class="hash-link" aria-label="Direct link to Correct the most recent local commit" title="Direct link to Correct the most recent local commit" translate="no">​</a></h3>
<p>If it has not been pushed:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git add path/to/fix</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit --amend</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="reverse-a-published-commit">Reverse a published commit<a href="https://smiler488.com/blog/gitHub-beginner-guide#reverse-a-published-commit" class="hash-link" aria-label="Direct link to Reverse a published commit" title="Direct link to Reverse a published commit" translate="no">​</a></h3>
<p>Create a new commit that reverses the selected commit:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git log --oneline</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git revert &lt;commit-hash&gt;</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Avoid destructive history changes on shared branches</div><div class="admonitionContent_BuS1"><p><code>git reset --hard</code> discards local work, and force-pushing rewrites shared history. They are not routine beginner recovery tools. Make a backup branch and confirm the collaboration policy before using either.</p></div></div>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="8-useful-inspection-commands">8. Useful inspection commands<a href="https://smiler488.com/blog/gitHub-beginner-guide#8-useful-inspection-commands" class="hash-link" aria-label="Direct link to 8. Useful inspection commands" title="Direct link to 8. Useful inspection commands" translate="no">​</a></h2>
<table><thead><tr><th>Command</th><th>Purpose</th></tr></thead><tbody><tr><td><code>git status</code></td><td>Show branch and working-tree state</td></tr><tr><td><code>git diff</code></td><td>Show unstaged changes</td></tr><tr><td><code>git diff --staged</code></td><td>Show staged changes</td></tr><tr><td><code>git log --oneline --graph --decorate --all</code></td><td>Inspect branch history</td></tr><tr><td><code>git remote -v</code></td><td>Show remote names and URLs</td></tr><tr><td><code>git branch -vv</code></td><td>Show branches and their upstreams</td></tr><tr><td><code>git show &lt;commit&gt;</code></td><td>Inspect one commit</td></tr></tbody></table>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="final-checklist">Final checklist<a href="https://smiler488.com/blog/gitHub-beginner-guide#final-checklist" class="hash-link" aria-label="Direct link to Final checklist" title="Direct link to Final checklist" translate="no">​</a></h2>
<p>Before pushing:</p>
<ul>
<li class="">no secrets or private data are staged;</li>
<li class="">generated files are excluded unless intentionally versioned;</li>
<li class="">the diff matches the commit message;</li>
<li class="">tests or builds relevant to the change pass;</li>
<li class="">the destination branch and remote are correct.</li>
</ul>
<p>For more detail, use the official <a href="https://git-scm.com/doc" target="_blank" rel="noopener noreferrer" class="">Git documentation</a> and <a href="https://docs.github.com/" target="_blank" rel="noopener noreferrer" class="">GitHub Docs</a>.</p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Git" term="Git"/>
        <category label="Reproducible Research" term="Reproducible Research"/>
        <category label="Web Development" term="Web Development"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[macOS Keyboard Shortcuts — A Practical Reference]]></title>
        <id>https://smiler488.com/blog/macOS-shortcuts</id>
        <link href="https://smiler488.com/blog/macOS-shortcuts"/>
        <updated>2021-02-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A compact, safety-aware reference for everyday macOS, Finder, text-editing, browser, screenshot, and recovery shortcuts.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="at-a-glance">At a glance<a href="https://smiler488.com/blog/macOS-shortcuts#at-a-glance" class="hash-link" aria-label="Direct link to At a glance" title="Direct link to At a glance" translate="no">​</a></h2>
<p>These shortcuts cover the actions most useful in research and development work: finding files, navigating text, managing windows, capturing evidence, and recovering from an unresponsive app.</p>
<ul>
<li class="">Shortcuts can vary by app and keyboard layout.</li>
<li class="">On newer keyboards, the <strong>Fn/Globe</strong> key may open the Character Viewer.</li>
<li class="">Recovery shortcuts can discard unsaved work; read their warnings before using them.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="modifier-key-legend">Modifier-key legend<a href="https://smiler488.com/blog/macOS-shortcuts#modifier-key-legend" class="hash-link" aria-label="Direct link to Modifier-key legend" title="Direct link to Modifier-key legend" translate="no">​</a></h2>
<table><thead><tr><th>Symbol</th><th>Key</th></tr></thead><tbody><tr><td><code>⌘</code></td><td>Command</td></tr><tr><td><code>⌥</code></td><td>Option / Alt</td></tr><tr><td><code>⌃</code></td><td>Control</td></tr><tr><td><code>⇧</code></td><td>Shift</td></tr><tr><td><code>Fn</code> / <code>🌐</code></td><td>Function / Globe</td></tr></tbody></table>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="search-apps-and-windows">Search, apps, and windows<a href="https://smiler488.com/blog/macOS-shortcuts#search-apps-and-windows" class="hash-link" aria-label="Direct link to Search, apps, and windows" title="Direct link to Search, apps, and windows" translate="no">​</a></h2>
<ul>
<li class=""><code>⌘ Space</code> — open Spotlight.</li>
<li class=""><code>⌘ Tab</code> — switch to the next open app; keep holding Command to choose an app.</li>
<li class=""><code>⌘ H</code> — hide all windows of the front app.</li>
<li class=""><code>⌥ ⌘ H</code> — hide windows of every app except the front app.</li>
<li class=""><code>⌘ M</code> — minimize the front window.</li>
<li class=""><code>⌘ W</code> — close the front window or tab.</li>
<li class=""><code>⌘ Q</code> — quit the front app.</li>
<li class=""><code>⌃ ⌘ Q</code> — lock the screen.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="screenshots-and-screen-recording">Screenshots and screen recording<a href="https://smiler488.com/blog/macOS-shortcuts#screenshots-and-screen-recording" class="hash-link" aria-label="Direct link to Screenshots and screen recording" title="Direct link to Screenshots and screen recording" translate="no">​</a></h2>
<ul>
<li class=""><code>⇧ ⌘ 3</code> — capture the entire screen.</li>
<li class=""><code>⇧ ⌘ 4</code> — capture a selected region; press Space after invoking it to capture a window.</li>
<li class=""><code>⇧ ⌘ 5</code> — open screenshot and screen-recording controls.</li>
</ul>
<p>Use <code>⌃</code> with a screenshot shortcut when you want to copy the capture to the clipboard instead of saving a file.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="finder">Finder<a href="https://smiler488.com/blog/macOS-shortcuts#finder" class="hash-link" aria-label="Direct link to Finder" title="Direct link to Finder" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="files">Files<a href="https://smiler488.com/blog/macOS-shortcuts#files" class="hash-link" aria-label="Direct link to Files" title="Direct link to Files" translate="no">​</a></h3>
<ul>
<li class=""><code>⌘ Delete</code> — move selected items to the Trash.</li>
<li class=""><code>⇧ ⌘ Delete</code> — empty the Trash after confirmation.</li>
<li class=""><code>⌘ I</code> — show information for the selected item.</li>
<li class=""><code>⌘ D</code> — duplicate the selected item.</li>
<li class=""><code>Space</code> — preview the selected item with Quick Look.</li>
</ul>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="navigation">Navigation<a href="https://smiler488.com/blog/macOS-shortcuts#navigation" class="hash-link" aria-label="Direct link to Navigation" title="Direct link to Navigation" translate="no">​</a></h3>
<ul>
<li class=""><code>⌘ ↑</code> — open the enclosing folder.</li>
<li class=""><code>⌘ ↓</code> — open the selected item.</li>
<li class=""><code>⇧ ⌘ G</code> — go to a folder by path.</li>
<li class=""><code>⌘ 1</code>, <code>⌘ 2</code>, <code>⌘ 3</code>, <code>⌘ 4</code> — switch among icon, list, column, and gallery views.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="text-editing-and-navigation">Text editing and navigation<a href="https://smiler488.com/blog/macOS-shortcuts#text-editing-and-navigation" class="hash-link" aria-label="Direct link to Text editing and navigation" title="Direct link to Text editing and navigation" translate="no">​</a></h2>
<p>These work in most native text fields and many editors:</p>
<ul>
<li class=""><code>⌘ A</code> — select all.</li>
<li class=""><code>⌘ C</code>, <code>⌘ X</code>, <code>⌘ V</code> — copy, cut, and paste.</li>
<li class=""><code>⌘ Z</code> — undo.</li>
<li class=""><code>⇧ ⌘ Z</code> — redo in apps that follow the standard convention.</li>
<li class=""><code>⌥ ←</code> / <code>⌥ →</code> — move one word backward or forward.</li>
<li class=""><code>⌘ ←</code> / <code>⌘ →</code> — move to the beginning or end of the current line.</li>
<li class=""><code>Fn Delete</code> — forward delete on compact keyboards.</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="safari-and-chrome">Safari and Chrome<a href="https://smiler488.com/blog/macOS-shortcuts#safari-and-chrome" class="hash-link" aria-label="Direct link to Safari and Chrome" title="Direct link to Safari and Chrome" translate="no">​</a></h2>
<ul>
<li class=""><code>⌘ T</code> — open a new tab.</li>
<li class=""><code>⌘ W</code> — close the current tab.</li>
<li class=""><code>⇧ ⌘ T</code> — reopen the most recently closed tab.</li>
<li class=""><code>⌘ L</code> — focus the address bar.</li>
<li class=""><code>⌘ R</code> — reload the page.</li>
<li class=""><code>⌘ [</code> / <code>⌘ ]</code> — go backward or forward in tab history.</li>
</ul>
<p>Browser extensions and web apps may override some shortcuts.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="recovery-and-power-controls">Recovery and power controls<a href="https://smiler488.com/blog/macOS-shortcuts#recovery-and-power-controls" class="hash-link" aria-label="Direct link to Recovery and power controls" title="Direct link to Recovery and power controls" translate="no">​</a></h2>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>Save work first</div><div class="admonitionContent_BuS1"><p>The following actions can close apps or discard unsaved changes. Use them only when the normal app or Apple menu controls do not work.</p></div></div>
<ul>
<li class=""><code>⌥ ⌘ Esc</code> — open Force Quit Applications.</li>
<li class="">Press and hold the power button — force the Mac to turn off when it is unresponsive.</li>
<li class=""><code>⌃ ⌘ Power</code> — on supported built-in keyboards without Touch ID, force a restart without prompting to save open documents.</li>
<li class=""><code>⌃ ⌥ ⌘ Power</code> — on supported built-in keyboards without Touch ID, ask apps to quit and then shut down; apps with unsaved documents may prompt first.</li>
</ul>
<p>Power-key behavior differs across Touch ID keyboards, external keyboards, and macOS versions. Prefer <strong>Apple menu → Shut Down</strong> or <strong>Restart</strong> whenever the system still responds.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="official-reference">Official reference<a href="https://smiler488.com/blog/macOS-shortcuts#official-reference" class="hash-link" aria-label="Direct link to Official reference" title="Direct link to Official reference" translate="no">​</a></h2>
<p>Apple maintains the authoritative and most current list at <a href="https://support.apple.com/en-us/102650" target="_blank" rel="noopener noreferrer" class="">Mac keyboard shortcuts</a>.</p>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Productivity" term="Productivity"/>
        <category label="Reproducible Research" term="Reproducible Research"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Build and Publish a Personal Website with Docusaurus and GitHub Pages]]></title>
        <id>https://smiler488.com/blog/personal-website-docusaurus-github-pages</id>
        <link href="https://smiler488.com/blog/personal-website-docusaurus-github-pages"/>
        <updated>2021-01-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A version-aware workflow for creating a Docusaurus portfolio, configuring GitHub Pages correctly, and maintaining a reliable deployment.]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="project-overview">Project overview<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#project-overview" class="hash-link" aria-label="Direct link to Project overview" title="Direct link to Project overview" translate="no">​</a></h2>
<p>This guide builds a personal portfolio with Docusaurus and publishes the generated static site to GitHub Pages. It covers the two details that cause most deployment failures: choosing the correct <code>baseUrl</code> and keeping the source branch separate from the generated <code>gh-pages</code> branch.</p>
<ul>
<li class=""><strong>Best for:</strong> portfolios, project documentation, publication lists, and technical notes</li>
<li class=""><strong>Result:</strong> a version-controlled site with repeatable local and hosted builds</li>
<li class=""><strong>Current baseline:</strong> Docusaurus 3 requires Node.js 20 or newer</li>
</ul>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="before-you-start">Before you start<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#before-you-start" class="hash-link" aria-label="Direct link to Before you start" title="Direct link to Before you start" translate="no">​</a></h2>
<p>Install or create:</p>
<ul>
<li class="">Node.js 20 or newer and npm</li>
<li class="">Git</li>
<li class="">a GitHub account</li>
<li class="">a code editor</li>
</ul>
<p>Check the local tools:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">node --version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm --version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git --version</span><br></div></code></pre></div></div>
<p>The commands below use npm. Keep one package manager and its lockfile throughout the project.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="1-create-the-site">1. Create the site<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#1-create-the-site" class="hash-link" aria-label="Direct link to 1. Create the site" title="Direct link to 1. Create the site" translate="no">​</a></h2>
<p>Use the official project generator:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx create-docusaurus@latest my-portfolio classic</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd my-portfolio</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm install</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm run start</span><br></div></code></pre></div></div>
<p>The development server normally opens at <code>http://localhost:3000</code>. Changes to Markdown, React components, and CSS appear through hot reload.</p>
<p>The classic template includes:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">my-portfolio/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── blog/                  # dated Markdown or MDX posts</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── docs/                  # documentation and long-form pages</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── src/</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── components/        # reusable React components</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── css/custom.css     # site-wide styling</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── pages/             # standalone routes</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── static/                # files copied directly to the build</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── docusaurus.config.js</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── sidebars.js</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└── package.json</span><br></div></code></pre></div></div>
<p>There is no default <code>npm run new blog</code> command. Create a file such as <code>blog/2026-07-16-field-workflow.md</code> and add valid front matter.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="2-decide-the-github-pages-address">2. Decide the GitHub Pages address<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#2-decide-the-github-pages-address" class="hash-link" aria-label="Direct link to 2. Decide the GitHub Pages address" title="Direct link to 2. Decide the GitHub Pages address" translate="no">​</a></h2>
<p>The repository name determines the public path.</p>
<table><thead><tr><th>Site type</th><th>Repository</th><th>Public URL</th><th><code>baseUrl</code></th></tr></thead><tbody><tr><td>User or organization site</td><td><code>&lt;username&gt;.github.io</code></td><td><code>https://&lt;username&gt;.github.io/</code></td><td><code>/</code></td></tr><tr><td>Project site</td><td><code>my-portfolio</code></td><td><code>https://&lt;username&gt;.github.io/my-portfolio/</code></td><td><code>/my-portfolio/</code></td></tr></tbody></table>
<p>Configure <code>docusaurus.config.js</code> for one target. This user-site example preserves clean root URLs:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> config </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">title</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"My Portfolio"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">url</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"https://&lt;username&gt;.github.io"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">baseUrl</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">organizationName</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;username&gt;"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">projectName</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;username&gt;.github.io"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">deploymentBranch</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"gh-pages"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token literal-property property" style="color:#36acaa">trailingSlash</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">false</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword module" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword module" style="color:#00009f">default</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>For a project site, change <code>projectName</code> to the repository name and set <code>baseUrl</code> to <code>/repository-name/</code>. Do not leave this value to an unset environment variable in the deployment workflow.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="3-add-content-and-identity">3. Add content and identity<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#3-add-content-and-identity" class="hash-link" aria-label="Direct link to 3. Add content and identity" title="Direct link to 3. Add content and identity" translate="no">​</a></h2>
<p>Prioritize information visitors need:</p>
<ul>
<li class="">a short research or professional profile</li>
<li class="">current projects with concrete outcomes</li>
<li class="">publications, datasets, software, and reproducible workflows</li>
<li class="">contact routes with a clear purpose</li>
<li class="">a concise blog with dated, maintained articles</li>
</ul>
<p>Put downloadable assets under <code>static/</code>. For example, <code>static/files/cv.pdf</code> is available at <code>/files/cv.pdf</code>.</p>
<p>Use the blog author registry rather than repeating job titles inside every post. Updating one author record prevents old identity text from remaining in archived articles.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="4-verify-the-production-build">4. Verify the production build<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#4-verify-the-production-build" class="hash-link" aria-label="Direct link to 4. Verify the production build" title="Direct link to 4. Verify the production build" translate="no">​</a></h2>
<p>Run the same build locally before publishing:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npm run build</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">npm run serve</span><br></div></code></pre></div></div>
<p>The preview uses the generated <code>build/</code> directory. Resolve broken links, invalid front matter, and missing assets before deployment.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="5-connect-the-source-repository">5. Connect the source repository<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#5-connect-the-source-repository" class="hash-link" aria-label="Direct link to 5. Connect the source repository" title="Direct link to 5. Connect the source repository" translate="no">​</a></h2>
<p>Create an empty GitHub repository, then connect the local project:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git init</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git add .</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "feat: create personal website"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git branch -M main</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git remote add origin https://github.com/&lt;username&gt;/&lt;repository&gt;.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push -u origin main</span><br></div></code></pre></div></div>
<p>The source branch contains editable code. The <code>gh-pages</code> branch should contain only generated deployment files.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="6-deploy-with-github-actions">6. Deploy with GitHub Actions<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#6-deploy-with-github-actions" class="hash-link" aria-label="Direct link to 6. Deploy with GitHub Actions" title="Direct link to 6. Deploy with GitHub Actions" translate="no">​</a></h2>
<p>Create <code>.github/workflows/deploy.yml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Deploy to GitHub Pages</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">push</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">branches</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">permissions</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">contents</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> write</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">jobs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">deploy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">runs-on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">latest</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">steps</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">uses</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> actions/checkout@v4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">uses</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> actions/setup</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">node@v4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">with</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">node-version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">20</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">cache</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> npm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> npm ci</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> npm run build</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Publish generated site</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">uses</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> peaceiris/actions</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">gh</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pages@v4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">with</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">github_token</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> secrets.GITHUB_TOKEN </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">publish_dir</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ./build</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">publish_branch</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gh</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pages</span><br></div></code></pre></div></div>
<p>If the source branch is <code>master</code>, update the workflow trigger accordingly. In <strong>Settings → Pages</strong>, select the <code>gh-pages</code> branch and its root directory as the publishing source.</p>
<p>For a controlled manual deployment, Docusaurus also provides:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">npx docusaurus deploy</span><br></div></code></pre></div></div>
<p>Use one deployment method consistently. A project-specific <code>npm run deploy</code> script may wrap the build and publish commands.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="7-maintenance-workflow">7. Maintenance workflow<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#7-maintenance-workflow" class="hash-link" aria-label="Direct link to 7. Maintenance workflow" title="Direct link to 7. Maintenance workflow" translate="no">​</a></h2>
<p>For each update:</p>
<ol>
<li class="">edit on a feature branch when the change is substantial;</li>
<li class="">run the production build;</li>
<li class="">review the generated page on desktop and mobile;</li>
<li class="">commit the source change;</li>
<li class="">push the configured source branch and monitor the deployment job.</li>
</ol>
<p>Periodically review dependency release notes before upgrading all <code>@docusaurus/*</code> packages to the same version.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="troubleshooting">Troubleshooting<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#troubleshooting" class="hash-link" aria-label="Direct link to Troubleshooting" title="Direct link to Troubleshooting" translate="no">​</a></h2>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="styles-or-assets-return-404">Styles or assets return 404<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#styles-or-assets-return-404" class="hash-link" aria-label="Direct link to Styles or assets return 404" title="Direct link to Styles or assets return 404" translate="no">​</a></h3>
<p>Confirm that <code>url</code>, <code>baseUrl</code>, and the repository name describe the same hosting path. A project site deployed with <code>baseUrl: '/'</code> is the most common cause.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="a-route-works-through-navigation-but-not-at-its-public-url">A route works through navigation but not at its public URL<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#a-route-works-through-navigation-but-not-at-its-public-url" class="hash-link" aria-label="Direct link to A route works through navigation but not at its public URL" title="Direct link to A route works through navigation but not at its public URL" translate="no">​</a></h3>
<p>Docusaurus generates static routes rather than relying on a generic single-page-app fallback. Set an explicit <code>trailingSlash</code> policy, verify the generated files, and keep GitHub Pages publishing from the expected branch.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="the-action-can-build-but-cannot-publish">The action can build but cannot publish<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#the-action-can-build-but-cannot-publish" class="hash-link" aria-label="Direct link to The action can build but cannot publish" title="Direct link to The action can build but cannot publish" translate="no">​</a></h3>
<p>Confirm <code>permissions: contents: write</code>, repository Actions permissions, the source-branch trigger, and the Pages publishing branch.</p>
<h3 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="a-custom-domain-does-not-resolve">A custom domain does not resolve<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#a-custom-domain-does-not-resolve" class="hash-link" aria-label="Direct link to A custom domain does not resolve" title="Direct link to A custom domain does not resolve" translate="no">​</a></h3>
<p>Configure the custom domain in GitHub Pages and at the DNS provider. Subdomains normally use a CNAME record; apex domains use supported A, ALIAS, or ANAME records. Keep <code>static/CNAME</code> only when the deployment workflow requires the file to be included in every build.</p>
<h2 class="anchor anchorTargetHideOnScrollNavbar_vjPI" id="official-references">Official references<a href="https://smiler488.com/blog/personal-website-docusaurus-github-pages#official-references" class="hash-link" aria-label="Direct link to Official references" title="Direct link to Official references" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://docusaurus.io/docs/installation" target="_blank" rel="noopener noreferrer" class="">Docusaurus installation</a></li>
<li class=""><a href="https://docusaurus.io/docs/deployment" target="_blank" rel="noopener noreferrer" class="">Docusaurus deployment</a></li>
<li class=""><a href="https://docs.github.com/pages" target="_blank" rel="noopener noreferrer" class="">GitHub Pages documentation</a></li>
</ul>]]></content>
        <author>
            <name>Liangchao Deng</name>
            <uri>https://github.com/smiler488</uri>
        </author>
        <category label="Web Development" term="Web Development"/>
        <category label="Git" term="Git"/>
        <category label="Reproducible Research" term="Reproducible Research"/>
    </entry>
</feed>