<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Jeremy's Programming Blog]]></title><description><![CDATA[My blog About Software Development]]></description><link>https://www.jeremymorgan.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1649352221946/oTvfKLiJA.png</url><title>Jeremy&apos;s Programming Blog</title><link>https://www.jeremymorgan.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 09 Sep 2026 07:17:00 GMT</lastBuildDate><atom:link href="https://www.jeremymorgan.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[SQL to Cypher - 10 Queries You Already Know]]></title><description><![CDATA[In March 2016, npm removed an 11-line package called left-pad. Within minutes, builds began failing across the JavaScript ecosystem. It broke thousands of projects, including tools like Babel. Many de]]></description><link>https://www.jeremymorgan.dev/sql-to-cypher-10-queries-you-already-know</link><guid isPermaLink="true">https://www.jeremymorgan.dev/sql-to-cypher-10-queries-you-already-know</guid><category><![CDATA[Neo4j]]></category><category><![CDATA[Neo4J Enterprise]]></category><category><![CDATA[graph database]]></category><category><![CDATA[SQL]]></category><category><![CDATA[SQLite]]></category><category><![CDATA[SQL vs NoSQL]]></category><category><![CDATA[sql Queries]]></category><category><![CDATA[graph theory]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Mon, 10 Aug 2026 22:30:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5f1cae3af4016901885cc4dd/002c2bd4-ee11-4566-8a86-ea88eb074531.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In March 2016, npm removed an 11-line package called left-pad. Within minutes, builds began failing across the JavaScript ecosystem. It broke thousands of projects, including tools like Babel. Many developers didn't choose left-pad directly; it was hidden in their dependencies and went unnoticed until it vanished.</p>
<p>That incident points at a question you have probably asked about your own stack: what is actually in my dependency tree? Not just the 30 packages in your <code>package.json</code>. Everything they pull in, and everything those pull in, all the way down.</p>
<p>In a relational database, dependencies live in a self-referencing join table. "Everything, all the way down" means a recursive CTE. Here is that query on a snapshot of the npm registry. It finds the full runtime dependency tree of <code>express</code>:</p>
<pre><code class="language-sql">WITH RECURSIVE closure(name, depth) AS (
  SELECT depends_on_name, 1
  FROM dependencies
  WHERE package_name = 'express' AND dep_type = 'runtime'
  UNION
  SELECT d.depends_on_name, c.depth + 1
  FROM closure c
  JOIN dependencies d
    ON d.package_name = c.name AND d.dep_type = 'runtime'
)
SELECT count(DISTINCT name) AS transitive_deps, max(depth) AS max_depth
FROM closure;
</code></pre>
<p>Here is the result:</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f1cae3af4016901885cc4dd/3da8ee4d-9e00-4072-9142-7278f94f6d8d.png" alt="&quot;Query showing runtime dependencies&quot;" style="display:block;margin:0 auto" />

<p>It works. Here it shows 63 packages, 11 levels deep. But it is twelve lines, and every line matters. There is an anchor part, a recursive part, and a <code>UNION</code> doing quiet work to remove duplicates. A graph asks the same question in two lines:</p>
<pre><code class="language-cypher">MATCH (:Package {name: 'express'})-[:DEPENDS_ON*]-&gt;(dep)
RETURN count(DISTINCT dep) AS transitive_deps
</code></pre>
<p>That is not a shortened excerpt. That is the whole query. It returns the same 63 packages.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f1cae3af4016901885cc4dd/d412872d-f484-4551-b7ae-38bee5580755.webp" alt="&quot;Query showing runtime dependencies&quot;" style="display:block;margin:0 auto" />

<p>Cypher is Neo4j's query language. For a SQL developer, it is less a new language than a new notation for questions you already know how to ask. This article proves that claim with ten translations. They run from "this is just SQL with arrows" up to the query above, plus one final twist that SQL has no clean answer for. By the end, you should be able to read Cypher on sight and write the first seven queries yourself, today.</p>
<h2>The dataset, so you can check my work</h2>
<p>Every query runs against a fixed snapshot of npm. It starts with the top 5,000 most-used packages, then follows their runtime dependencies until the set is complete. The result is about 5,600 packages, their maintainers, and every dependency link among them. The exact same data exists twice.</p>
<p><strong>Relational (SQLite):</strong> four tables. This is the shape the data naturally takes in a relational model.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f1cae3af4016901885cc4dd/ae115a49-582e-4f39-8b9c-55ba5ff82120.webp" alt="Relational Diagram of our Model" style="display:block;margin:0 auto" />

<ul>
<li><p><code>packages(package_name, description, license, latest_version, weekly_downloads, deprecated)</code></p>
</li>
<li><p><code>dependencies(package_name, depends_on_name, dep_type, semver_range)</code>, the join table</p>
</li>
<li><p><code>maintainers(username)</code> and <code>package_maintainers(username, package_name)</code></p>
</li>
</ul>
<p><strong>Graph (Neo4j):</strong> the same facts as nodes and relationships.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f1cae3af4016901885cc4dd/a7556418-45cd-4c5a-983a-49b835ec1c1f.webp" alt="Graph Diagram of our Model" style="display:block;margin:0 auto" />

<ul>
<li><p><code>(:Package)</code> and <code>(:Maintainer)</code> nodes with the same properties</p>
</li>
<li><p><code>(:Package)-[:DEPENDS_ON {range}]-&gt;(:Package)</code> for runtime dependencies</p>
</li>
<li><p><code>(:Package)-[:DEV_DEPENDS_ON {range}]-&gt;(:Package)</code> for dev dependencies</p>
</li>
<li><p><code>(:Maintainer)-[:MAINTAINS]-&gt;(:Package)</code></p>
</li>
</ul>
<h3>From join table to relationships</h3>
<p>Notice where the join table went. Each row of <code>dependencies</code> became a relationship. Its columns split in an interesting way. The <code>semver_range</code> is a fact about the dependency itself, so it became a property on the relationship. But <code>dep_type</code> became the relationship's <em>type</em>. Runtime and dev dependencies are different kinds of link, not different values in a column. Query 3 will show why that choice pays off.</p>
<p>Note: this snapshot collapses versions. There is one node per package, and the links come from each package's <code>latest</code> version. The semver ranges are saved as text but not resolved. So the graph answers "what does the current ecosystem depend on," not "what exact files does <code>npm install</code> produce." Version-level modeling is a different and much bigger graph.</p>
<p>Both databases, the CSVs, and the build pipeline are in the <a href="https://github.com/JeremyMorgan/sql-to-cypher">companion repository</a>. You can load the graph into a free <a href="https://neo4j.com/product/auradb/">Aura</a> instance by pasting <a href="https://github.com/JeremyMorgan/sql-to-cypher/blob/main/load_aura.cypher">this script</a>. Or download <code>npm.db</code> and follow along in SQLite.</p>
<h2>Queries 1 to 3: the automatic translations</h2>
<p>For filtering, sorting, and picking columns, Cypher works just like SQL. The main change is how you point at your data. Instead of FROM packages p, you write MATCH (p:Package). The parentheses draw a node, the label picks which kind, and p is an alias, doing the same job it does in SQL.</p>
<h3>1. SELECT with WHERE</h3>
<p>Start with every MIT-licensed package:</p>
<pre><code class="language-sql">SELECT package_name, description
FROM packages
WHERE license = 'MIT';
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5f1cae3af4016901885cc4dd/c8299ecd-7867-4525-94c6-3d6a65afed6c.png" alt="&quot;Results of Query from SQL&quot;" style="display:block;margin:0 auto" />

<p>The same query in Cypher:</p>
<pre><code class="language-cypher">MATCH (p:Package)              
WHERE p.license = 'MIT'        
RETURN p.name, p.description   
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5f1cae3af4016901885cc4dd/607218f2-03d8-4e60-9305-947eee279113.webp" alt="&quot;Results of Query from Cypher&quot;" style="display:block;margin:0 auto" />

<p>And as you can see the output is identical to the result of the SQL query.</p>
<h3>2. ORDER BY and LIMIT</h3>
<p>Now the ten most-downloaded packages:</p>
<pre><code class="language-sql">SELECT package_name, weekly_downloads
FROM packages
ORDER BY weekly_downloads DESC
LIMIT 10;
</code></pre>
<p>The same question in Cypher:</p>
<pre><code class="language-cypher">MATCH (p:Package)
RETURN p.name, p.weeklyDownloads
ORDER BY p.weeklyDownloads DESC
LIMIT 10
</code></pre>
<p>Same keywords, same meaning. There is nothing to learn here. That is the point.</p>
<h3>3. Your first JOIN becomes a pattern</h3>
<p>The direct runtime dependencies of <code>express</code>:</p>
<pre><code class="language-sql">SELECT d.depends_on_name, d.semver_range
FROM packages p
JOIN dependencies d ON d.package_name = p.package_name
WHERE p.package_name = 'express'
  AND d.dep_type = 'runtime';
</code></pre>
<p>In Cypher:</p>
<pre><code class="language-cypher">MATCH (:Package {name: 'express'})-[d:DEPENDS_ON]-&gt;(dep:Package)
RETURN dep.name, d.range
</code></pre>
<p>This is the first translation where the shape changes, not just the keywords. The <code>JOIN ... ON</code> clause is gone. The connection between two packages is stored as a relationship, so the arrow <code>-[:DEPENDS_ON]-&gt;</code> <em>is</em> the join, already built.</p>
<p>Look at what happened to <code>AND d.dep_type = 'runtime'</code>. It disappeared. Runtime and dev dependencies are different relationship types, so asking for <code>DEPENDS_ON</code> filters by itself. Want dev dependencies instead? Write <code>-[:DEV_DEPENDS_ON]-&gt;</code>. Want both? Write <code>-[:DEPENDS_ON|DEV_DEPENDS_ON]-&gt;</code>.</p>
<p>In SQL, this is the filter you always have to remember, and the bug you ship when you forget it. In the graph, it became part of the <em>question's</em> grammar.</p>
<p>Both queries return <strong>28</strong> rows. Express pulls in <strong>28</strong> packages directly. Hold that number. Query 10 will tell you what it grows into.</p>
<h2>Queries 4 to 6: aggregation without GROUP BY</h2>
<h3>4. The join you write twice</h3>
<p>The dependencies of express's dependencies, two levels down, by hand:</p>
<pre><code class="language-sql">SELECT DISTINCT d2.depends_on_name
FROM dependencies d1
JOIN dependencies d2
  ON d2.package_name = d1.depends_on_name
 AND d2.dep_type = 'runtime'
WHERE d1.package_name = 'express'
  AND d1.dep_type = 'runtime';
</code></pre>
<p>The same question in Cypher:</p>
<pre><code class="language-cypher">MATCH (:Package {name: 'express'})-[:DEPENDS_ON*2]-&gt;(dep:Package)
RETURN DISTINCT dep.name
</code></pre>
<p>Each extra level in SQL costs another self-join of the <code>dependencies</code> table. You add <code>d3</code>, <code>d4</code>, <code>d5</code>, each with its own <code>ON</code> clause and its own <code>dep_type</code> filter to not forget.</p>
<p>In Cypher, depth is a number. Write <code>*2</code> for exactly two hops, or <code>*1..3</code> for one to three. This query is a preview. You can feel where hand-written depth stops working, and query 10 is waiting there.</p>
<h3>5. GROUP BY is implicit</h3>
<p>Which packages have the most direct dependencies?</p>
<pre><code class="language-sql">SELECT p.package_name, COUNT(*) AS dep_count
FROM packages p
JOIN dependencies d ON d.package_name = p.package_name
WHERE d.dep_type = 'runtime'
GROUP BY p.package_name
ORDER BY dep_count DESC
LIMIT 10;
</code></pre>
<p>In Cypher:</p>
<pre><code class="language-cypher">MATCH (p:Package)-[:DEPENDS_ON]-&gt;()
RETURN p.name, count(*) AS depCount
ORDER BY depCount DESC
LIMIT 10
</code></pre>
<p>There is no <code>GROUP BY</code> in Cypher. After a week, you will not miss it. The rule is simple. Any plain expression that appears next to an aggregate in <code>RETURN</code> (or <code>WITH</code>) becomes a grouping key. Since <code>p.name</code> sits next to <code>count(*)</code>, Cypher groups by it. This also removes SQL's most tedious error: the "column must appear in the GROUP BY clause" dance, where you repeat every selected column a second time.</p>
<p>One more detail. The empty node <code>()</code> is anonymous. We are counting edges to anything, so the target needs no name. Say only what you need.</p>
<h3>6. HAVING becomes WITH ... WHERE</h3>
<p>The load-bearing packages: the ones that more than 100 other packages depend on directly:</p>
<pre><code class="language-sql">SELECT d.depends_on_name, COUNT(*) AS dependents
FROM dependencies d
WHERE d.dep_type = 'runtime'
GROUP BY d.depends_on_name
HAVING COUNT(*) &gt; 100
ORDER BY dependents DESC;
</code></pre>
<p>The same question in Cypher:</p>
<pre><code class="language-cypher">MATCH (p:Package)&lt;-[:DEPENDS_ON]-()
WITH p, count(*) AS dependents
WHERE dependents &gt; 100
RETURN p.name, dependents
ORDER BY dependents DESC
</code></pre>
<p>Two things happened here. First, the arrow flipped. The pattern <code>&lt;-[:DEPENDS_ON]-</code> counts <em>incoming</em> links: who depends on <code>p</code>, not what <code>p</code> depends on. Direction is visible in the pattern. SQL hides it in which column you group by. Quick, was it <code>package_name</code> or <code>depends_on_name</code>?</p>
<p>Second, meet <code>WITH</code>. It is the construct SQL never gave you: a pipe between query stages. Aggregate, then filter the result, then keep going. <code>HAVING</code> exists in SQL only because <code>WHERE</code> runs before aggregation. Cypher does not need a special keyword. You place the <code>WHERE</code> after the aggregation in the pipeline. One mental model replaces two keywords.</p>
<p>Reach for <code>WITH</code> any time you catch yourself wanting a subquery or a second CTE stage. It covers most of both.</p>
<h2>Queries 7 to 9: the patterns that replace subqueries</h2>
<h3>7. LEFT JOIN ... IS NULL becomes OPTIONAL MATCH</h3>
<p>Leaf packages: in the dataset, but nothing else depends on them:</p>
<pre><code class="language-sql">SELECT p.package_name
FROM packages p
LEFT JOIN dependencies d
  ON d.depends_on_name = p.package_name
 AND d.dep_type = 'runtime'
WHERE d.package_name IS NULL;
</code></pre>
<p>The anti-join translates straight across. <code>OPTIONAL MATCH</code> is exactly <code>LEFT JOIN</code>: match if you can, bind <code>NULL</code> if you cannot, then keep the rows where the match failed.</p>
<pre><code class="language-cypher">MATCH (p:Package)
OPTIONAL MATCH (p)&lt;-[d:DEPENDS_ON]-()
WITH p, d
WHERE d IS NULL
RETURN p.name
</code></pre>
<p>But the graph does not need the join at all. The <code>LEFT JOIN ... IS NULL</code> pattern makes you assemble every non-match just to count that it is missing. The real question is simpler: <em>does anything depend on</em> <code>p</code><em>?</em> An <code>EXISTS</code> predicate in <code>WHERE</code> asks exactly that:</p>
<pre><code class="language-cypher">MATCH (p:Package)
WHERE NOT EXISTS { (p)&lt;-[:DEPENDS_ON]-() }
RETURN p.name
</code></pre>
<p>This is where the graph pulls ahead. <code>EXISTS { }</code> is an existence check: it stops the moment one incoming <code>DEPENDS_ON</code> is found and never enumerates the rest. Because Neo4j stores the relationship degree on every node, the question, does <code>p</code> have any incoming <code>DEPENDS_ON</code>, is answered by reading one number, without ever expanding a relationship or building a row to discard. It is a small query doing something the relational model has to work for, and a clean example of a graph-native advantage.</p>
<blockquote>
<p>A fun detail about this dataset: the leaves are mostly the famous top-of-tree packages, like <code>express</code>, <code>chalk</code>, and <code>lodash</code>. They start dependency trees rather than sit inside them. The snapshot contains what they depend on, not the millions of apps that depend on them.</p>
</blockquote>
<h3>8. EXISTS subqueries become pattern predicates</h3>
<p>Packages that depend on both <code>react</code> and <code>vue</code>. Rarer than you might think:</p>
<pre><code class="language-sql">SELECT p.package_name
FROM packages p
WHERE EXISTS (
  SELECT 1 FROM dependencies d
  WHERE d.package_name = p.package_name
    AND d.depends_on_name = 'react' AND d.dep_type = 'runtime')
AND EXISTS (
  SELECT 1 FROM dependencies d
  WHERE d.package_name = p.package_name
    AND d.depends_on_name = 'vue' AND d.dep_type = 'runtime');
</code></pre>
<p>The same query in Cypher:</p>
<pre><code class="language-cypher">MATCH (p:Package)
WHERE EXISTS { (p)-[:DEPENDS_ON]-&gt;(:Package {name: 'react'}) }
  AND EXISTS { (p)-[:DEPENDS_ON]-&gt;(:Package {name: 'vue'}) }
RETURN p.name
</code></pre>
<p>Same keyword as SQL, and you have already met it: query 7 used <code>NOT EXISTS { }</code> to find packages that nothing depends on. Here it does the positive job, with far less machinery. A correlated SQL subquery is a full nested query. It has its own <code>FROM</code>, its own filters, and its own correlation condition you wire up by hand. Cypher's <code>EXISTS { }</code> takes a pattern. The correlation is free, because the pattern reuses the variable <code>p</code> that is already bound. Ten lines become four, and the four read like a sentence.</p>
<p><em>By the way, if you're following along and running these queries yourself, the result is zero in this snapshot.</em></p>
<h3>9. The self-join</h3>
<p>Packages that share a maintainer with <code>lodash</code>:</p>
<pre><code class="language-sql">SELECT DISTINCT pm2.package_name
FROM package_maintainers pm1
JOIN package_maintainers pm2
  ON pm1.username = pm2.username
WHERE pm1.package_name = 'lodash'
  AND pm2.package_name &lt;&gt; 'lodash';
</code></pre>
<p>In Cypher:</p>
<pre><code class="language-cypher">MATCH (:Package {name: 'lodash'})&lt;-[:MAINTAINS]-(:Maintainer)-[:MAINTAINS]-&gt;(p:Package)
RETURN DISTINCT p.name
</code></pre>
<p>The SQL joins the association table against itself. That takes two aliases, a join on the shared key, and an inequality to skip the starting row. The Cypher walks <em>through</em> the shared maintainer instead. It goes into lodash from its maintainer, then back out to everything else that maintainer touches. There are no aliases and no inequality, since a path cannot reuse the relationship it arrived on. The query reads like the question itself: who else does lodash's maintainer maintain?</p>
<p>This pattern (entity, through a shared link, to entity) is the skeleton of collaborative filtering, fraud-ring detection, and "people also bought." In SQL it is always a self-join on the association table. In a graph it is always this V-shaped walk. Learn it once here.</p>
<h2>Query 10: where the languages stop being comparable</h2>
<p>Back to the opening question, now with the numbers filled in. Express declares 28 direct dependencies. Its full runtime tree, the true answer to "what am I actually installing," is <strong>63 packages, reaching 11 levels deep</strong>. The recursive CTE at the top of this article computes that correctly. I want to be precise about what is and is not hard about it.</p>
<p>The naive CTE finishes just fine on this dataset. Dependency graphs are nearly free of cycles, and the <code>UNION</code> step keeps the working set small by removing duplicates. This is a friendlier graph than, say, a social network. There, loops and highly connected nodes can make path-listing CTEs blow up.</p>
<p>What are the honest differences? First, the CTE is a program and the pattern is an expression. Twelve lines describe <em>how</em> to traverse: the anchor, the recursion, the dedup, the depth tracking. One line states <em>what</em> you want.</p>
<p>Second, and this is the one that matters in practice: now reverse the question. Not "what does express depend on," but the left-pad question. <em>What breaks if this package disappears?</em> In SQL, that is a second CTE, a mirror image of the first. The recursion now runs along <code>package_name</code> instead of <code>depends_on_name</code>. The anchor flips. You maintain both queries forever:</p>
<pre><code class="language-sql">WITH RECURSIVE blast_radius(name) AS (
  SELECT package_name FROM dependencies
  WHERE depends_on_name = 'debug' AND dep_type = 'runtime'
  UNION
  SELECT d.package_name
  FROM blast_radius b
  JOIN dependencies d ON d.depends_on_name = b.name
                     AND d.dep_type = 'runtime'
)
SELECT count(*) AS affected FROM blast_radius;
</code></pre>
<p>In Cypher, reversing the question means reversing the arrow:</p>
<pre><code class="language-cypher">MATCH (affected:Package)-[:DEPENDS_ON*]-&gt;(:Package {name: 'debug'})
RETURN count(DISTINCT affected) AS affected
</code></pre>
<p>Same query. Flipped arrow.</p>
<p>This is not Cypher being shorter for its own sake. The query language has a native concept, <em>the path</em>, that SQL lacks. Once paths are built in, "everything downstream," "everything upstream," "shortest chain between," and "is there any connection at all" become expressions instead of programs.</p>
<p>That is the difference between a notation gap and a capability gap. It is also the clearest signal for <strong>when</strong> a graph database is the right tool. If your hard queries are about how things connect, at unknown depth, then you are implementing algorithms in your query language that Cypher gives you as syntax.</p>
<h2>The cheat sheet</h2>
<p>The full mapping, for reference:</p>
<table>
<thead>
<tr>
<th>You know this in SQL</th>
<th>You write this in Cypher</th>
<th>Translation difficulty</th>
</tr>
</thead>
<tbody><tr>
<td><code>FROM table alias</code></td>
<td><code>MATCH (alias:Label)</code></td>
<td>Automatic</td>
</tr>
<tr>
<td><code>WHERE</code></td>
<td><code>WHERE</code> (or inline <code>{prop: value}</code>)</td>
<td>Automatic</td>
</tr>
<tr>
<td><code>SELECT</code></td>
<td><code>RETURN</code> (at the end)</td>
<td>Automatic</td>
</tr>
<tr>
<td><code>ORDER BY</code> / <code>LIMIT</code> / <code>DISTINCT</code></td>
<td>Same keywords</td>
<td>Automatic</td>
</tr>
<tr>
<td><code>JOIN ... ON</code></td>
<td>A relationship in the pattern: <code>-[:REL]-&gt;</code></td>
<td>Shape change</td>
</tr>
<tr>
<td>A type/discriminator column</td>
<td>A relationship <em>type</em></td>
<td>Modeling shift</td>
</tr>
<tr>
<td><code>GROUP BY</code></td>
<td>Implicit: plain terms next to aggregates</td>
<td>Shape change</td>
</tr>
<tr>
<td><code>HAVING</code></td>
<td><code>WITH ... WHERE</code></td>
<td>Shape change</td>
</tr>
<tr>
<td><code>LEFT JOIN</code></td>
<td><code>OPTIONAL MATCH</code></td>
<td>Automatic</td>
</tr>
<tr>
<td><code>EXISTS (subquery)</code></td>
<td><code>EXISTS { pattern }</code></td>
<td>Shape change</td>
</tr>
<tr>
<td>Self-join on an association table</td>
<td>The V-walk: <code>(a)&lt;-[:R]-(x)-[:R]-&gt;(b)</code></td>
<td>Modeling shift</td>
</tr>
<tr>
<td>Recursive CTE</td>
<td><code>-[:REL*]-&gt;</code>, direction by arrow</td>
<td>Different capability</td>
</tr>
</tbody></table>
<p>Look at the rightmost column. Most rows are automatic or close to it. You can read those today. A few rows each require one new idea: implicit grouping, the <code>WITH</code> pipeline, or joins as stored relationships. Only the last row is truly new territory, and it is the row you came for.</p>
<h2>When to stay in SQL</h2>
<p>This comparison is only honest if it cuts both ways. Nothing here argues for replacing your relational database wholesale. In fact, several of these queries are evidence <em>against</em> moving certain workloads:</p>
<ul>
<li><p><strong>Flat filtering and aggregation</strong> (queries 1, 2, and 5): SQL is at parity. Your team already knows it, and the tooling around it (BI layers, ORMs, decades of operational habit) is mature. A <code>GROUP BY</code> over one table is not a graph problem.</p>
</li>
<li><p><strong>Bulk reporting</strong> over wide tables with few relationship hops is exactly what relational engines are tuned for.</p>
</li>
<li><p><strong>The modeling cost is real.</strong> The graph's elegance was paid for at load time. Someone decided that <code>dep_type</code> should be a relationship type, that maintainership should be an edge, and that versions should collapse. Change a decision, rebuild the graph. If your access patterns are stable, tabular, and shallow, that cost never pays off.</p>
</li>
</ul>
<p>The signal to reach for a graph is specific. Your queries routinely chain three or more joins. The depth is variable or unknown ("all upstream dependencies," "how are these two things connected"). Or you find yourself writing recursive CTEs against self-referencing tables. That last one is the tell. If query 10 looks like something in your codebase, this article was written for you.</p>
<h2>Try all ten yourself</h2>
<p>Everything above is reproducible. The <a href="https://github.com/JeremyMorgan/sql-to-cypher">companion repository</a> has both databases prebuilt: <code>npm.db</code> for SQLite, and one paste-and-run script that loads the graph into a free <a href="https://neo4j.com/product/auradb/">Aura</a> instance. The CSVs and the pipeline that built them are there too, if you want to regenerate the snapshot yourself. No setup appears in this article because none is needed to follow it. The repo README is the whole install.</p>
<p>But the fastest route to <em>writing</em> Cypher rather than reading it is a course with a graph already loaded and live checking.  </p>
<p><a href="https://graphacademy.neo4j.com/courses/cypher-fundamentals"><strong>Cypher Fundamentals</strong></a> is free, runs in the browser, and takes about an hour. It covers exactly the constructs in queries 1 through 8, and turns "I can read this" into "I wrote this" in one sitting:</p>
<p>From there, <a href="https://graphacademy.neo4j.com/courses/cypher-intermediate-queries"><strong>Intermediate Cypher Queries</strong></a> picks up the variable-length paths and the <code>WITH</code> pipeline behind queries 6 and 10.</p>
<p>One teaser before you go, because query 10 probably raised the question. The dataset in this article is generic npm. Your <code>package-lock.json</code> is the same graph shape, with your project as the root node. Converting it to this schema is a small script away. Running a blast-radius query against your own application's real dependency tree is a great next step.</p>
<h2>Frequently asked questions</h2>
<p><strong>Is Cypher hard to learn if I already know SQL?</strong> No, and this article is the argument. Most of the ten translations are simple keyword swaps. The ideas that take real learning are implicit grouping (query 5), the <code>WITH</code> pipeline (query 6), and thinking of joins as stored relationships (query 3). This tends to click quickly for SQL developers.</p>
<p><strong>Do I have to give up SQL to use Neo4j?</strong> No. The common production pattern is to use both. The relational database keeps the transactional, tabular workloads. The graph handles the relationship-heavy queries. The dataset in this article exists in both databases at once, built from the same CSVs.</p>
<p><strong>Is Cypher actually faster, or just shorter?</strong> Shorter is what this article shows. Faster depends on the workload, and it deserves measurement. The structural argument goes like this: graph traversal follows stored pointers (called index-free adjacency) instead of recomputing joins at every hop. So cost scales with the part of the graph you touch, not the size of the tables you join. That difference grows with depth. For single-hop lookups, a well-indexed relational database is extremely hard to beat. And the naive recursive CTE handled this nearly cycle-free dataset without drama.</p>
<p><strong>Why did</strong> <code>dep_type</code> <strong>become a relationship type instead of a property?</strong> Because runtime and dev dependencies are different <em>kinds</em> of connection, and queries almost always want one kind or the other. Making the difference structural means the common case needs no filter, and the forgotten-filter bug cannot happen. The semver range stayed a property because it is a fact about an edge you already selected, not a way of selecting edges. That is the general rule. Things you filter on want to be types. Things you read afterward want to be properties.</p>
<p><strong>Does Cypher work outside Neo4j?</strong></p>
<p>Increasingly, yes. Cypher was the main input to GQL, the ISO-standard graph query language published in 2024. Open implementations of Cypher exist for several other engines. The skills transfer.</p>
]]></content:encoded></item><item><title><![CDATA[Six Degrees of Ayrton Senna: Learn Neo4j by Connecting 75 Years of Formula 1]]></title><description><![CDATA[One of my favorite things about F1 racing is the data behind it. F1 cars are the most complex and advanced in any racing series. They collect huge amounts of telemetry data. The tracks also gather dat]]></description><link>https://www.jeremymorgan.dev/six-degrees-of-ayrton-senna-learn-neo4j-by-connecting-75-years-of-formula-1</link><guid isPermaLink="true">https://www.jeremymorgan.dev/six-degrees-of-ayrton-senna-learn-neo4j-by-connecting-75-years-of-formula-1</guid><category><![CDATA[data]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[Databases]]></category><category><![CDATA[database]]></category><category><![CDATA[Neo4j]]></category><category><![CDATA[Graph]]></category><category><![CDATA[graph database]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Thu, 23 Jul 2026 22:22:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5f1cae3af4016901885cc4dd/21e38b20-2a78-4c64-a402-d7397380380a.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of my favorite things about F1 racing is the data behind it. F1 cars are the most complex and advanced in any racing series. They collect huge amounts of telemetry data. The tracks also gather data during events, and race engineers analyze it week after week. They study everything from weather, tire temperatures to corner exit speeds. Data drives the sport forward in a major way.</p>
<p>While learning about graph databases and Neo4j I realized it was the perfect tool for answering a question I was curious about. We've all seen or heard of "Six Degrees of Kevin Bacon" where nearly any actor can be traced back to the Footloose star. I wondered: could this work for F1 drivers? By comparison it's a much smaller dataset than famous actors.</p>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ud4euc1zbrer1eynyc2j.webp" alt="Max Verstappen and Juan Manuel Fangio" style="display:block;margin:0 auto" />

<p><em>Photos via Wikimedia Commons, licensed under CC BY‑SA 4.0.</em></p>
<p><strong>Is Max Verstappen connected to Juan Manuel Fangio? Could a driver who retired in 1958, decades before Max was born, connect to him through a chain of teammates? And if so, how many links does it take?</strong></p>
<p>This post is how I answered that, and it doubles as a gentle introduction to Neo4j and graph databases. By the end you'll have built a real graph of every F1 driver since 1950 on your own machine, and you'll run a query that answers my Verstappen-to-Fangio question in a single line. No prior graph experience needed.</p>
<p>Let's get into it.</p>
<hr />
<h2>Why this is a graph problem</h2>
<p>Before we start: My question isn't really about drivers. It's about the <em>connections between</em> drivers.</p>
<p>If all I wanted was a list of drivers, or each driver's win count, or how many races happened at Monza, a plain old relational table handles that beautifully. Even a spreadsheet can do it. Spreadsheets are wonderful at facts <em>about things</em>. Where they start to sweat is questions about <em>relationships</em> and <em>chains of relationships</em>. Specifically long relationship chains.</p>
<p>Think about what "is Verstappen connected to Fangio?" actually requires. You don't know in advance whether the answer is three hops or nine. So in SQL you'd be writing a recursive common table expression that joins a results table to itself, over and over, to a depth you can't predict, while trying not to drown in duplicate paths.</p>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/7wt49mrmba2q9vlf8n46.webp" alt="SQLite out of Memory" style="display:block;margin:0 auto" />

<p>I tried do this very thing and locked up the application trying. It's possible to do queries like this, but they rarely run fast, if they run at all. Relational databases weren't designed for things like this.</p>
<p>A graph database flips the whole thing around. Instead of storing drivers in one table and hoping to reconstruct their connections later with joins, it stores the <strong>connections themselves</strong> as useful entities. That's the one idea underneath everything else in this post:</p>
<blockquote>
<p>In a graph database, the relationships are first class data. They're not something you compute at query time. They're something you store, traverse, and count directly.</p>
</blockquote>
<p>That single design choice is what turns this tough question into a one liner. Let me show you the model before we build it.</p>
<hr />
<h2>The property graph model, in four pieces</h2>
<p>Neo4j uses the <strong>Labeled Property Graph</strong> model. It sounds fancy; it's just four building blocks. I'll introduce each one using our F1 data.</p>
<p><strong>Nodes</strong> are the things in your domain. The entities. For us, that's drivers and teams. In a diagram you draw them as circles. <code>Ayrton Senna</code> is a node. <code>McLaren</code> is a node.</p>
<p><strong>Labels</strong> are the <em>type</em> of a node, written with a colon: <code>:Driver</code>, <code>:Constructor</code>. (Constructor is just F1's official word for "team".) Labels are how Neo4j knows a Senna node is a driver and a McLaren node is a team. By convention they're written in PascalCase.</p>
<p><strong>Relationships</strong> are the connections between nodes, and this is where graphs shine. Every relationship has a type in SCREAMING_SNAKE_CASE, a direction, and a start and end node. <code>Senna DROVE_FOR McLaren</code> is a relationship. Crucially, that connection is stored in the database. Neo4j keeps a pointer from one node to the next. This is why hopping across relationships stays fast even when your graph gets huge. The cost of following one relationship remains small, whether your database has a thousand nodes or a billion.</p>
<p><strong>Properties</strong> are key-value pairs you can hang on <em>either</em> a node or a relationship. A <code>:Driver</code> node has <code>forename: 'Ayrton'</code>, <code>surname: 'Senna'</code>, <code>nationality: 'Brazilian'</code>. Here's something that might be surprising if you come from a table world: a <em>relationship</em> can carry properties too. Our <code>DROVE_FOR</code> relationship will carry <code>season: 1988</code>, because <em>which season</em> someone drove for a team is a fact about the connection, not about the driver or the team on their own.</p>
<p>That last point is worth thinking about, because it was an "aha" moment for relational-to-graph thinking. Senna drove for McLaren, but <em>when</em> he did it doesn't belong to Senna and doesn't belong to McLaren. It belongs to the link between them. Put it on the relationship and a whole category of modeling headaches evaporates.</p>
<p>Here's our entire starting model:</p>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qljya10patmhp2y0e6js.webp" alt="Data Model Ayrton Senna" style="display:block;margin:0 auto" />

<pre><code class="language-cypher">(:Driver {forename: 'Ayrton', surname: 'Senna'})
  -[:DROVE_FOR {season: 1988}]-&gt;
(:Constructor {name: 'McLaren'})
</code></pre>
<p>Two circles, one labeled arrow between them. If you want to make that concrete right now, open <a href="https://arrows.neo4jlabs.com">Arrows</a> (a free browser diagramming tool) and draw it. Click to make a node, give it a label and some properties, drag from its edge to a second node to create the relationship. It's helpful to sketch your schema there before writing any code.</p>
<hr />
<h2>Design for the question you want to ask</h2>
<p>Before building anything, I did the single most useful thing you can do when modeling a graph: I wrote down the question I actually wanted to answer, <em>first</em>, and let it drive every decision afterward.</p>
<p>My question — "how are two drivers connected through shared teammates?" This tells me exactly what my graph needs. It needs drivers. It needs some notion of two drivers <em>being teammates</em>. Everything else is optional scaffolding.</p>
<p>But notice the dataset doesn't hand me "teammate" directly. It gives me who drove for which team in which season. Two drivers are teammates when they drove for the <em>same team</em> in the <em>same season</em>.</p>
<p>So my plan has a nice shape to it:</p>
<ol>
<li><p>Load <code>Driver</code> and <code>Constructor</code> nodes.</p>
</li>
<li><p>Connect them with <code>DROVE_FOR</code> relationships (one per driver, per team, per season).</p>
</li>
<li><p><strong>Derive</strong> a brand-new <code>TEAMMATE_OF</code> relationship between any two drivers who share a team and season.</p>
</li>
<li><p>Walk the <code>TEAMMATE_OF</code> web to answer my question.</p>
</li>
</ol>
<p>In step three we are creating relationships that weren't in the raw data, by reasoning about the graph you already have. This is one of my favorite things about working in Neo4j, and you'll see why shortly. Let's build.</p>
<hr />
<h2>What you'll need</h2>
<ul>
<li><p><strong>Neo4j Desktop</strong> — free, from <a href="https://neo4j.com/download/">neo4j.com/download</a>. I'm on the current version (Desktop 2.x) on a Mac; Windows and Linux are the same journey.</p>
</li>
<li><p><strong>The dataset</strong> — the "Formula 1 World Championship (1950–2024)" dataset by Rohan Rao on <a href="https://www.kaggle.com/datasets/rohanrao/formula-1-world-championship-1950-2020">Kaggle</a>. Free Kaggle account, one download, clean CSVs.</p>
</li>
<li><p><strong>An afternoon.</strong> Realistically a couple of hours, most of it spent going "oh that's cool" at the results.</p>
</li>
<li><p><strong>No prior Cypher.</strong> I'll explain every query as we go. Cypher is Neo4j's query language and it's genuinely readable. If you already know SQL you'll be nodding along within minutes.</p>
</li>
</ul>
<blockquote>
<p><strong>A quick note on the data:</strong> For about a decade, the go-to source for F1 data was the Ergast API. It shut down at the end of 2024. The Kaggle dataset we're using preserves Ergast's exact structure as downloadable CSVs, and if you later want live current-season data, the community-run <strong>Jolpica-F1 API</strong> (<code>api.jolpi.ca/ergast/f1/</code>) serves the same schema. Build against the CSVs today; top up from Jolpica whenever you like. Nothing in this tutorial changes.</p>
</blockquote>
<p>Once Neo4j Desktop is installed: under local instances, click <code>create instance</code> to create a new local instance.</p>
<p>Give it a name and a password you'll remember, and start it. Then open the <strong>Query</strong> tool (in Desktop 2.x this is where you run Cypher — it's the modern replacement for what older tutorials call "Neo4j Browser"). That's our workbench.</p>
<p>We need four files from the Kaggle download:</p>
<ul>
<li><p><code>drivers.csv</code></p>
</li>
<li><p><code>constructors.csv</code></p>
</li>
<li><p><code>races.csv</code></p>
</li>
<li><p><code>results.csv</code></p>
</li>
</ul>
<p>We can stage these files for import by placing them in our imports folder. Select your local instance, and look for the <code>...</code> button.</p>
<p>Select <code>Open</code> then <code>Instance folder</code>:</p>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/opuwo04q4556z1e03xtz.webp" alt="Neo4j Desktop Import" style="display:block;margin:0 auto" />

<p>This is the folder for your Neo4j instance. Next select the <code>import</code> folder. This is where you want to place the files.</p>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/qh168hbgr2ud9fw2j8km.webp" alt="Neo4j Desktop Import" style="display:block;margin:0 auto" />

<p><strong>macOS</strong>:</p>
<pre><code class="language-plaintext">/Users/[your username]/neo4j-community-2026.x/import
</code></pre>
<p><strong>Windows</strong>:</p>
<pre><code class="language-plaintext">C:\Neo4j\import
</code></pre>
<p><strong>Linux</strong>:</p>
<pre><code class="language-plaintext">/var/lib/neo4j/import
</code></pre>
<p>Now that the files are in the import folder, we can access them with Cypher later.</p>
<hr />
<h2>Step 1: Constraints first</h2>
<p>Before loading a single row, I created uniqueness constraints. A constraint guarantees you'll never accidentally create two copies of the same driver. Neo4j automatically builds an index behind each one, which makes all the lookups during import dramatically faster.</p>
<p>In Neo4j Desktop you can run queries by selecting query from the left hand panel and entering your queries in the window in the upper right.</p>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/j5e6qe6rwvp06hx11qw4.webp" alt="Neo4j Query Window" style="display:block;margin:0 auto" />

<p>Here's the query to create the constraints:</p>
<pre><code class="language-cypher">CREATE CONSTRAINT driver_id IF NOT EXISTS
FOR (d:Driver) REQUIRE d.driverId IS UNIQUE;

CREATE CONSTRAINT constructor_id IF NOT EXISTS
FOR (c:Constructor) REQUIRE c.constructorId IS UNIQUE;

CREATE CONSTRAINT race_id IF NOT EXISTS
FOR (r:Race) REQUIRE r.raceId IS UNIQUE;
</code></pre>
<p>Run <code>SHOW CONSTRAINTS</code> to confirm all three landed. That's your data-integrity seatbelt fastened.</p>
<hr />
<h2>Step 2: Load the nodes</h2>
<p>Now we bring in the entities. <code>LOAD CSV</code> reads a file row by row; <code>MERGE</code> is Cypher's "create this if it doesn't already exist, otherwise match the existing one" command.</p>
<p><strong>Drivers:</strong></p>
<pre><code class="language-cypher">LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/JeremyMorgan/Six-Degrees-Senna/main/import/drivers.csv' AS row
MERGE (d:Driver {driverId: toInteger(row.driverId)})
SET d.forename  = row.forename,
  d.surname   = row.surname,
  d.fullName  = row.forename + ' ' + row.surname,
  d.nationality = row.nationality,
  d.dob   = CASE WHEN row.dob &lt;&gt; '\\N' THEN date(row.dob) END;
</code></pre>
<p>That <code>CASE WHEN row.dob &lt;&gt; '\\N'</code> is guarding against a quirk you'll hit constantly with this dataset: missing values are stored as the literal text <code>\N</code>. If you don't filter them out, you'll end up with drivers whose birthday is the string "backslash-N", which is exactly as useful as it sounds. Consider that your first real-world data-cleaning lesson, delivered by Formula 1.</p>
<p><strong>Constructors:</strong></p>
<pre><code class="language-cypher">LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/JeremyMorgan/Six-Degrees-Senna/main/import/constructors.csv' AS row
MERGE (c:Constructor {constructorId: toInteger(row.constructorId)})
SET c.name    = row.name,
  c.nationality = row.nationality;
</code></pre>
<p><strong>Races</strong> (we mostly need these to know which season a result belongs to, but full Race nodes cost nothing and set you up for future projects):</p>
<pre><code class="language-cypher">LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/JeremyMorgan/Six-Degrees-Senna/main/import/races.csv' AS row
MERGE (r:Race {raceId: toInteger(row.raceId)})
SET r.year  = toInteger(row.year),
  r.round = toInteger(row.round),
  r.name  = row.name,
  r.date  = date(row.date);
</code></pre>
<p>Quick check: you should see something in the ballpark of 861 drivers, 212 constructors, and 1,100-plus races:</p>
<pre><code class="language-cypher">MATCH (n) RETURN labels(n)[0] AS label, count(*) ORDER BY label;
</code></pre>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/fkzy6y9kxduz4nhzpj0d.webp" alt="Cypher Query Results" style="display:block;margin:0 auto" />

<p>If those numbers look right, you've just loaded three-quarters of a century of motorsport into a graph. We haven't done anything clever yet, but we're about to.</p>
<hr />
<h2>Step 3: Connect drivers to teams</h2>
<p>The <code>results.csv</code> file has one row per driver, per race — around 26,000 rows. I don't want 26,000 relationships cluttering my graph. I want one clean fact per driver, per team, per season: <em>this person drove for this team that year</em>. So I aggregate as I load.</p>
<pre><code class="language-cypher">:auto
LOAD CSV WITH HEADERS FROM 'https://raw.githubusercontent.com/JeremyMorgan/Six-Degrees-Senna/main/import/results.csv' AS row
CALL (row) {
  MATCH (d:Driver  {driverId:  toInteger(row.driverId)})
  MATCH (c:Constructor {constructorId: toInteger(row.constructorId)})
  MATCH (r:Race    {raceId:    toInteger(row.raceId)})
  MERGE (d)-[s:DROVE_FOR {season: r.year, constructorId: c.constructorId}]-&gt;(c)
  ON CREATE SET s.entries = 1,
      s.wins  = CASE WHEN row.positionOrder = '1' THEN 1 ELSE 0 END
  ON MATCH  SET s.entries = s.entries + 1,
      s.wins  = s.wins + CASE WHEN row.positionOrder = '1' THEN 1 ELSE 0 END
} IN TRANSACTIONS OF 2000 ROWS;
</code></pre>
<p>There's a lot of learning packed into that one statement, so let's unpack it:</p>
<ul>
<li><p><code>MERGE (d)-[:DROVE_FOR {season, constructorId}]-&gt;(c)</code> is the key move. The <em>first</em> time we see Senna-at-McLaren-in-1988, this creates the relationship. Every <em>subsequent</em> race that season just finds the existing one and bumps its counters. Twenty-six thousand rows collapse into roughly 3,600 clean driver-season-team facts. This is the graph-modeling principle in action: <strong>store relationships at the granularity you plan to query.</strong></p>
</li>
<li><p><code>ON CREATE</code> <strong>/</strong> <code>ON MATCH</code> let you do one thing when the relationship is brand new and a different thing when it already exists — here, initialize the counters versus increment them.</p>
</li>
<li><p><code>:auto</code> <strong>and</strong> <code>IN TRANSACTIONS OF 2000 ROWS</code> tell Neo4j to commit the import in batches rather than one giant transaction, which keeps memory happy on a big file.</p>
</li>
</ul>
<blockquote>
<p><strong>Version note:</strong> <code>CALL (row) { … }</code> is the modern syntax (Neo4j 5.23 and up) for passing a variable into a subquery. If your Neo4j is older, you'll get a syntax error on that line — use the legacy form <code>CALL { WITH row … }</code> instead. And don't copy an abbreviated snippet with <code>...</code> in the middle into the Query editor; Cypher will try to parse the dots. Use the full block above.</p>
</blockquote>
<p>Let's make sure it worked by looking at a career I know:</p>
<pre><code class="language-cypher">MATCH (d:Driver {surname:'Senna', forename:'Ayrton'})-[s:DROVE_FOR]-&gt;(c:Constructor)
RETURN c.name AS team, s.season AS season, s.wins AS wins
ORDER BY season;
</code></pre>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/tsab52va8ahk0j8g04tv.webp" alt="Ayrton Senna Record" style="display:block;margin:0 auto" />

<p>Toleman in '84, Lotus '85 to '87, McLaren '88 to '93, Williams in '94. If that's what you see, your graph is alive and correct.</p>
<hr />
<h2>Step 4: Derive the teammate network.</h2>
<p>Everything so far was setup. This is the payoff of graph thinking.</p>
<p>Nowhere in the data does it say "Senna and Prost were teammates." But we can <em>derive</em> that: two drivers are teammates if they each have a <code>DROVE_FOR</code> relationship to the <strong>same constructor</strong> with the <strong>same season</strong>. And in Cypher, describing that pattern is close to describing it in English:</p>
<pre><code class="language-cypher">MATCH (d1:Driver)-[r1:DROVE_FOR]-&gt;(c:Constructor)&lt;-[r2:DROVE_FOR]-(d2:Driver)
WHERE r1.season = r2.season
  AND d1.driverId &lt; d2.driverId
MERGE (d1)-[t:TEAMMATE_OF {season: r1.season, team: c.name}]-&gt;(d2);
</code></pre>
<p>Read that <code>MATCH</code> line like a picture: driver one points to a constructor, and driver two points to the <em>same</em> constructor from the other side. The <code>WHERE</code> says "same season." And then we <code>MERGE</code> a shiny new <code>TEAMMATE_OF</code> relationship between them.</p>
<p>We just created around 10,000 relationships that didn't exist in the source data, purely by reasoning about the shape of the graph.</p>
<p>Two small things worth understanding:</p>
<ul>
<li><p><code>d1.driverId &lt; d2.driverId</code> stops us creating each pairing twice (Senna→Prost <em>and</em> Prost→Senna). By only linking the lower ID to the higher one, each pair gets a single relationship. When we query it, we'll just ignore direction — because "teammate" goes both ways, and Cypher happily traverses a relationship in either direction when you leave the arrowhead off.</p>
</li>
<li><p><strong>A deliberately imperfect definition, and why I'm keeping it.</strong> "Same team, same season" isn't <em>exactly</em> "raced side by side." Midseason driver swaps mean, for example, that Senna and David Coulthard both count as 1994 Williams drivers. Coulthard was Senna's replacement, and they never actually raced as teammates. I could tighten this up by deriving teammate links per-race instead of per-season.</p>
</li>
</ul>
<p>I'm keeping the looser version on purpose, for two reasons. First, it makes the network richer and more connected across eras, which is the whole point. Second, <strong>every graph model is an argument about what a relationship</strong> <em><strong>means</strong></em>. There's no universally correct answer; there's only the definition that serves your question. Naming that trade-off out loud is important.</p>
<hr />
<h2>Step 5: Answer the question</h2>
<p>Here it is. The reason I built the whole thing. Two drivers separated by half a century, and one line of Cypher to connect them:</p>
<pre><code class="language-cypher">MATCH (max:Driver {surname:'Verstappen', forename:'Max'}),
  (fangio:Driver {surname:'Fangio'})
MATCH p = shortestPath((max)-[:TEAMMATE_OF*]-(fangio))
RETURN p;
</code></pre>
<p>That <code>*</code> after <code>TEAMMATE_OF</code> is the star of the show. It means "follow this relationship any number of times" — a <strong>variable-length path</strong>. <code>shortestPath</code> then finds the tightest chain of teammate links between the two drivers. This is the exact query that would've been a page of recursive SQL. In Cypher it fits on a napkin.</p>
<p>When you run it, the Query tool draws the answer as a chain of driver nodes, each link labeled with the team and season that connects them. This is how close Max really is to Fangio.</p>
<img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/oo4g40uco3f0ljrigct6.webp" alt="Neo4j Desktop Result Verstappen to Fangio" style="display:block;margin:0 auto" />

<p>Once that lands, you'll want to push further. Here are the three queries I couldn't stop running.</p>
<p><strong>Every driver's "Senna number"</strong> — like the Bacon number, but for F1. How many teammate-hops is each driver from Ayrton Senna?</p>
<pre><code class="language-cypher">MATCH (senna:Driver {surname:'Senna', forename:'Ayrton'})
MATCH (d:Driver) WHERE d &lt;&gt; senna
MATCH p = shortestPath((senna)-[:TEAMMATE_OF*..25]-(d))
RETURN length(p) AS sennaNumber, count(d) AS drivers
ORDER BY sennaNumber;
</code></pre>
<p>The shape of those results is the real insight: almost the entire history of the sport sits within a handful of hops of Senna. That's a "small-world network," demonstrated with race cars.</p>
<p><strong>The most connected drivers in history</strong> — the human bridges holding the whole web together:</p>
<pre><code class="language-cypher">MATCH (d:Driver)-[:TEAMMATE_OF]-(other:Driver)
RETURN d.fullName AS driver, count(DISTINCT other) AS teammates
ORDER BY teammates DESC
LIMIT 10;
</code></pre>
<p>Watch who tops this list: long career journeymen and team-hoppers, not necessarily the champions. Connectedness rewards longevity and movement, not podiums. I find that interesting. The people stitching F1's social fabric together are often not the ones holding the trophies.</p>
<p><strong>Is it really all one network?</strong> Are there isolated islands of drivers?</p>
<pre><code class="language-cypher">MATCH (d:Driver)
WHERE NOT (d)-[:TEAMMATE_OF]-()
RETURN count(d) AS unconnectedDrivers;
</code></pre>
<p>A small handful of true loners from F1's chaotic early days, and then one enormous connected web containing basically everyone else. Seventy-five years, one family.</p>
<hr />
<h2>What you just learned (it wasn't really about F1)</h2>
<ul>
<li><p><strong>The property graph model</strong> — nodes, labels, relationships, and properties, including the quietly powerful idea that a <em>relationship</em> can carry properties of its own.</p>
</li>
<li><p><strong>Designing for questions, not entities</strong> — writing the question first and letting it shape the model.</p>
</li>
<li><p><code>LOAD CSV</code><strong>,</strong> <code>MERGE</code><strong>, and constraints</strong> — the everyday mechanics of getting real data into Neo4j cleanly.</p>
</li>
<li><p><strong>Deriving new relationships</strong> — creating structure that wasn't in your source data by reasoning about the graph you already have.</p>
</li>
<li><p><strong>Variable-length paths and</strong> <code>shortestPath</code> — the thing graphs do effortlessly and relational databases do through gritted teeth.</p>
</li>
</ul>
<p>And here's the part that matters beyond motorsport: swap the dataset and every one of these skills transfers directly. The teammate network is structurally identical to a fraud ring, a supply chain, a social graph, an org chart, or the knowledge graph behind an AI application. "Who is connected to whom, and how?" is one of the most valuable questions in software, and you now know how to ask it.</p>
<hr />
<p><a href="https://github.com/JeremyMorgan/Six-Degrees-Senna">Download the code here</a></p>
<h2>Where to go next</h2>
<p>If this clicked for you the best next move is to get the fundamentals properly, in order. That's exactly what <a href="https://graphacademy.neo4j.com/"><strong>GraphAcademy</strong></a> is for. It's Neo4j's free, hands-on learning platform.</p>
<p>A path I'd suggest, roughly in this order:</p>
<ol>
<li><p><a href="https://graphacademy.neo4j.com/courses/neo4j-fundamentals"><strong>Neo4j Fundamentals</strong></a> — the property graph model, cemented, with exercises.</p>
</li>
<li><p><a href="https://graphacademy.neo4j.com/courses/cypher-fundamentals"><strong>Cypher Fundamentals</strong></a> — everything we hand-waved past today, done properly.</p>
</li>
<li><p><a href="https://graphacademy.neo4j.com/courses/importing-fundamentals"><strong>Importing data with Cypher</strong></a> — <code>LOAD CSV</code> and friends, beyond our quick version.</p>
</li>
</ol>
<p>For future articles I'm thinking: turn this same graph into a fair fight, deriving "who-beat-whom" links between teammates and running an algorithm called PageRank to settle the greatest-of-all-time argument without ever touching the points table.</p>
]]></content:encoded></item><item><title><![CDATA[I attended POST/CON 2025. Here are my thoughts]]></title><description><![CDATA[I just returned from POST/CON 25, and I’m left with one big impression: Postman gets it.
They get that we’re not just managing APIs. We’re building complex systems, and increasingly, those systems involve AI. And as AI technologies race ahead Postman...]]></description><link>https://www.jeremymorgan.dev/i-attended-postcon-2025-here-are-my-thoughts</link><guid isPermaLink="true">https://www.jeremymorgan.dev/i-attended-postcon-2025-here-are-my-thoughts</guid><category><![CDATA[Postman]]></category><category><![CDATA[#PostmanAPI]]></category><category><![CDATA[PostmanTesting]]></category><category><![CDATA[APIs]]></category><category><![CDATA[AI]]></category><category><![CDATA[mcp]]></category><category><![CDATA[mcp server]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Mon, 16 Jun 2025 17:09:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750093669529/f6bcd66d-088e-4828-b052-3d40106cc1e9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I just returned from POST/CON 25, and I’m left with one big impression: Postman gets it.</p>
<p>They get that we’re not just managing APIs. We’re building complex systems, and increasingly, those systems involve AI. And as AI technologies race ahead Postman is right there to help you keep up.</p>
<p>I’ve been using Postman for years, and I was thrilled to be invited to POST/CON 2025 in Los Angeles. It was an amazing experience, and I want to share some of the highlights.</p>
<p>If you want the full rundown of the conference, you can <a target="_blank" href="https://fnf.dev/4mSSgP1">check out the official recap here</a>. I want to share my personal highlights and thoughts on the event.</p>
<h2 id="heading-day-1-full-day-workshop-mastering-ai-agent-automation-with-postman-flows">Day 1: Full day workshop - “Mastering AI agent Automation with Postman Flows”</h2>
<p><img src="https://www.jeremymorgan.com/images/blog/programming/postcon-2025-highlights/postcon-2025-highlights-00.webp" alt="Postman POST/CON 2025 Highlights" /></p>
<p>There I learned:</p>
<p>🔥 How easy it is to connect things together with flow</p>
<p>🔥 How to tie together AI agents into a seamless workflow and deploy it</p>
<p>🔥 Learn best practices for design</p>
<p>Then a fireside chat with Ryan Reynolds and Justine Davis that had almost nothing to do with code and everything to do with building great products that solve real problems, and taking care of people.</p>
<p><img src="https://www.jeremymorgan.com/images/blog/programming/postcon-2025-highlights/postcon-2025-highlights-01.webp" alt="Postman POST/CON 2025 Highlights" /></p>
<h2 id="heading-day-2-keynote-and-sessions">Day 2: Keynote and Sessions</h2>
<p>Attended the Keynote where I learned about</p>
<p>🛠️ Postman VS code extensions</p>
<p>🛠️ GitHub Integration</p>
<p>🛠️ Postman Insights (lots of great analytics)</p>
<p>🛠️ AI Agent Builder</p>
<p>🛠️ Model Comparison Template</p>
<p>🛠️ MCP Server Generator (REALLY)</p>
<p>and so much more. You can <a target="_blank" href="https://fnf.dev/4mSSgP1">get all the details here</a>.</p>
<h3 id="heading-sessions">Sessions</h3>
<p>I attended some amazing sessions, where I learned a ton about</p>
<p>🧠 Agent Orchestration using API-driven workflows</p>
<p>🧠 Building multi-agent experiences with Agent Builder</p>
<p>🧠 Automating API Testing in CI/CD pipelines with CircleCI and Postman</p>
<p>🧠 Composable API Ecosystems and the MCP Protocol</p>
<p>and tons of great lightning talks.</p>
<h2 id="heading-one-of-the-best-conferences-ive-attended">One of the best conferences I’ve attended</h2>
<p><img src="https://www.jeremymorgan.com/images/blog/programming/postcon-2025-highlights/postcon-2025-highlights-02.webp" alt="Postman POST/CON 2025 Highlights" /></p>
<p>The energy at this conference was amazing. It’s one of the best I’ve attended. Postman is at the forefront of tech. They’ve created great tools to keep YOU ahead of the game. They focus on building their product well and help you create your best products, too.</p>
<p>I highly suggest going to the next POST/CON. If you do, let me know and let’s meet up!</p>
<p>Thank you so much to Postman for sending me to this conference. Now, if you’ll excuse me, I have some new tools to try out!</p>
<p>– Jeremy</p>
]]></content:encoded></item><item><title><![CDATA[Review: The New NVIDIA Jetson Orin Nano]]></title><description><![CDATA[Hello, friends! If you’re a reader of this blog you’ve probably heard about NVIDIA’s Jetson. It’s a great platform for prototyping apps and putting AI at the edge.
I got lucky and got my hands on the newest, very affordable Jetson, the Jetson Orin Na...]]></description><link>https://www.jeremymorgan.dev/review-the-new-nvidia-jetson-orin-nano</link><guid isPermaLink="true">https://www.jeremymorgan.dev/review-the-new-nvidia-jetson-orin-nano</guid><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Deep Learning]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[large language models]]></category><category><![CDATA[iot]]></category><category><![CDATA[edgecomputing]]></category><category><![CDATA[ Edge AI]]></category><category><![CDATA[NVIDIA]]></category><category><![CDATA[GPU, NVIDIA, AMD]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Thu, 26 Dec 2024 02:51:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735181275493/7d3100e4-ad12-4fe6-9bcc-e819100daf1c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello, friends! If you’re a reader of this blog you’ve probably heard about NVIDIA’s Jetson. It’s a great platform for prototyping apps and putting AI at the edge.</p>
<p>I got lucky and got my hands on the newest, very affordable Jetson, the Jetson Orin Nano.</p>
<p>Today, we’ll dive into everything from unboxing this little gem to testing its performance with AI models. Ready to explore what makes this device so cool? Let’s get started!</p>
<p>If you’d rather see a video version of this review <a target="_blank" href="https://www.youtube.com/watch?v=JRhAMHxlo3E">here it is</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=JRhAMHxlo3E">https://www.youtube.com/watch?v=JRhAMHxlo3E</a></div>
<p> </p>
<h3 id="heading-whats-inside-the-box">What’s Inside the Box?</h3>
<p><img src="https://www.jeremymorgan.com/images/blog/tech/nvidia-jetson-orin-nano/nvidia-jetson-orin-nano-00.webp" alt="“NVIDIA Jetson Orin Nano”" /></p>
<p>First impressions matter, and the Orin Nano’s packaging is as clean and straightforward as you’d expect. Here’s what you get:</p>
<ul>
<li><p>A robust power supply with both European and American plugs.</p>
</li>
<li><p>A quick start manual (kinda)</p>
</li>
<li><p>And, of course, the star of the show: the Jetson Orin Nano.</p>
</li>
</ul>
<p>You do need to purchase an SD card for it, aas it’s not shipped with one.</p>
<p>The device is sleek, lightweight, and full of potential. It includes HDMI, USB, Ethernet, GPIO, and video input ports. Whether you’re making an AI robot or building computer vision stuff (I know I will), this device has you covered.</p>
<hr />
<h3 id="heading-the-specs-why-its-called-a-raspberry-pi-on-steroids">The Specs: Why It’s Called a “Raspberry Pi on Steroids”</h3>
<p><img src="https://www.jeremymorgan.com/images/blog/tech/nvidia-jetson-orin-nano/nvidia-jetson-orin-nano-01.webp" alt="“NVIDIA Jetson Orin Nano”" /></p>
<p>Let’s talk hardware. Look at what’s packed into this small device:</p>
<ul>
<li><p><strong>CPU</strong>: A six-core ARM Cortex 64-bit processor.</p>
</li>
<li><p><strong>GPU</strong>: NVIDIA Ampere architecture, tuned for AI workloads.</p>
</li>
<li><p><strong>RAM</strong>: 8 GB of high-speed memory.</p>
</li>
<li><p><strong>Connectivity</strong>: Ethernet and wireless ready.</p>
</li>
<li><p><strong>Storage Options</strong>: NVMe support for all your AI model needs.</p>
</li>
</ul>
<p>Think of it as a supercharged Raspberry Pi with a serious focus on AI.</p>
<hr />
<h3 id="heading-setting-it-up-the-good-the-tricky-and-the-rewarding">Setting It Up: The Good, The Tricky, and The Rewarding</h3>
<p><img src="https://www.jeremymorgan.com/images/blog/tech/nvidia-jetson-orin-nano/nvidia-jetson-orin-nano-02.webp" alt="“NVIDIA Jetson Orin Nano”" /></p>
<p>Getting the Orin Nano up and running wasn’t exactly plug-and-play, but it wasn’t rocket science either. Here’s how it went down:</p>
<ol>
<li><p><strong>Firmware Update</strong>: Before diving in, I updated the firmware to version 36.x. This step required temporarily installing Jetpack 5.1.3. Then, you update the firmware from there. (Pro tip: don’t skip this. You want the new firmware and Jetpack)</p>
</li>
<li><p><strong>Jetpack 6.1 Installation</strong>: After the firmware update, I installed Jetpack 6.1, NVIDIA’s tailored OS for the Jetson lineup. It’s smooth, intuitive, and optimized for AI tasks.</p>
</li>
</ol>
<p>If you’re purchasing this devcie, then burning SD cards and flashing firmware shouldn’t be a problem.</p>
<hr />
<h3 id="heading-performance-tests-how-does-it-handle-ai-models">Performance Tests: How Does It Handle AI Models?</h3>
<p><img src="https://www.jeremymorgan.com/images/blog/tech/nvidia-jetson-orin-nano/nvidia-jetson-orin-nano-03.webp" alt="“NVIDIA Jetson Orin Nano”" /></p>
<p>Here’s where the fun begins—I tested the Orin Nano with AI models using <strong>Ollama</strong>, a platform designed for running large language models (LLMs).</p>
<p><a target="_blank" href="https://ollama.com/">Check out Ollama here</a></p>
<p>Here’s what I discovered:</p>
<ul>
<li><p><strong>1 Billion Parameter Models</strong>: Smooth sailing! These models ran effortlessly, perfect for tasks like chatbots or real-time applications.</p>
</li>
<li><p><strong>3 Billion Parameter Models</strong>: Still impressive, though with slightly longer loading times.</p>
</li>
<li><p><strong>7 Billion Parameter Models</strong>: Houston, we have a problem. The Orin Nano hit its limits here, occasionally freezing or locking up.</p>
</li>
</ul>
<p>While it’s not designed for massive models, this performance is impressive for a device in this price range.</p>
<p>You can view the <a target="_blank" href="https://youtu.be/JRhAMHxlo3E?si=sWhFLwRx5Dgu7bO4&amp;t=768">performance tests I did in this video</a>.</p>
<hr />
<h3 id="heading-pros-and-cons-the-real-deal">Pros and Cons: The Real Deal</h3>
<p><strong>What I Loved</strong>:</p>
<ol>
<li><p><strong>Compact and Lightweight</strong>: Perfect for edge AI projects where space is a premium.</p>
</li>
<li><p><strong>Impressive Performance</strong>: At $250, you’re getting serious bang for your buck.</p>
</li>
<li><p><strong>Quiet Operation</strong>: Even under load, it’s quiet—a big plus for home projects.</p>
</li>
<li><p><strong>User-Friendly OS</strong>: Jetpack is intuitive and packed with features.</p>
</li>
</ol>
<p><strong>What Needs Improvement</strong>:</p>
<ol>
<li><p><strong>Limits on Larger Models</strong>: It’s not a deal-breaker, but don’t expect it to handle anything beyond 7 billion parameters comfortably.</p>
</li>
<li><p><strong>Occasional Glitches</strong>: Some random lockups and an odd “system throttled due to overcurrent” error. I’m looking into it. Manageable, but worth noting.</p>
</li>
</ol>
<hr />
<h3 id="heading-final-verdict-is-it-worth-it">Final Verdict: Is It Worth It?</h3>
<p><img src="https://www.jeremymorgan.com/images/blog/tech/nvidia-jetson-orin-nano/nvidia-jetson-orin-nano-04.webp" alt="“NVIDIA Jetson Orin Nano”" /></p>
<p>Absolutely. The NVIDIA Jetson Orin Nano is a fantastic choice for developers exploring AI at the edge or prototyping innovative solutions.</p>
<p>It’s not built for heavy-duty production workloads, but for $250, it’s hard to beat the value and potential.</p>
<hr />
<h3 id="heading-whats-next">What’s Next?</h3>
<p>I’ll explore the Orin Nano’s features more in upcoming projects, so stay tuned. If you’re interested in edge AI or want to try out large language models, this device is a perfect start.</p>
<p>Have questions or suggestions for tests? Just drop a comment or message me on social media. Let’s see what this small device can really do!</p>
<p>Happy experimenting! 🚀</p>
<p>You can order the NVIDIA Jetson Orin Nano from the following places (Though right now there is no stock, check back!)</p>
<ul>
<li><p><a target="_blank" href="https://amzn.to/4gq4QS8">Amazon</a></p>
</li>
<li><p><a target="_blank" href="https://www.arrow.com/en/products/945-13766-0000-000/nvidia?nvid=em-945-13766-0005-000">Arrow Electronics</a></p>
</li>
<li><p><a target="_blank" href="https://www.sparkfun.com/products/22098?nvid=em-945-13766-0005-000">Sparkfun</a></p>
</li>
<li><p><a target="_blank" href="https://www.seeedstudio.com/NVIDIAr-Jetson-Orintm-Nano-Developer-Kit-p-5617.html?nvid=em-945-13766-0005-000">Seeed Studio</a></p>
</li>
</ul>
<p>Questions, comments? Want more details? <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">Yell at me</a>!!</p>
]]></content:encoded></item><item><title><![CDATA[How to Make a Retro 2D JavaScript Game Part 3]]></title><description><![CDATA[Note: If you'd rather have a video tutorial, here it is:


Let’s make the game more fun with scoring and difficulty progression.
1. Adding a Score
Modify the create function to display a score:
this.score = 0;
this.scoreText = this.add.text(16, 16, '...]]></description><link>https://www.jeremymorgan.dev/how-to-make-a-retro-2d-javascript-game-part-3</link><guid isPermaLink="true">https://www.jeremymorgan.dev/how-to-make-a-retro-2d-javascript-game-part-3</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[how to build games]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Games]]></category><category><![CDATA[how-to]]></category><category><![CDATA[phaser]]></category><category><![CDATA[2D Games]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[#beginners #learningtocode #100daysofcode]]></category><category><![CDATA[beginner]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Sun, 22 Dec 2024 22:07:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734905546943/e758cd73-af73-4532-b9c7-0a1e5d0654e4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Note: If you'd rather have a video tutorial, here it is:</em></p>
<iframe class="youtubevid" src="https://www.youtube.com/embed/rCgDvYNoaoc"></iframe>

<p>Let’s make the game more fun with scoring and difficulty progression.</p>
<h3 id="heading-1-adding-a-score"><strong>1. Adding a Score</strong></h3>
<p>Modify the <code>create</code> function to display a score:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">this</span>.score = <span class="hljs-number">0</span>;
<span class="hljs-built_in">this</span>.scoreText = <span class="hljs-built_in">this</span>.add.text(<span class="hljs-number">16</span>, <span class="hljs-number">16</span>, <span class="hljs-string">'Score: 0'</span>, { <span class="hljs-attr">fontSize</span>: <span class="hljs-string">'24px'</span>, <span class="hljs-attr">fill</span>: <span class="hljs-string">'#fff'</span> });
</code></pre>
<p>Now you'll see a score in the upper left hand corner of the screen:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734904806967/3be1a2f8-cb83-4474-b676-ce50fa28df03.png" alt /></p>
<p>Let’s update the score when the player catches an item. In the update function where we created collision detection, add the following to update the score:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">this</span>.score += <span class="hljs-number">10</span>;
<span class="hljs-built_in">this</span>.scoreText.setText(<span class="hljs-string">'Score: '</span> + <span class="hljs-built_in">this</span>.score);
</code></pre>
<p>So change this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (Phaser.Geom.Intersects.RectangleToRectangle(<span class="hljs-built_in">this</span>.player.getBounds(), <span class="hljs-built_in">this</span>.item.getBounds())) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Caught an item!'</span>);
        <span class="hljs-built_in">this</span>.item.y = <span class="hljs-number">50</span>;
        <span class="hljs-built_in">this</span>.item.x = Phaser.Math.Between(<span class="hljs-number">50</span>, <span class="hljs-number">750</span>);
}
</code></pre>
<p>to this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (Phaser.Geom.Intersects.RectangleToRectangle(<span class="hljs-built_in">this</span>.player.getBounds(), <span class="hljs-built_in">this</span>.item.getBounds())) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Caught an item!'</span>);
        <span class="hljs-built_in">this</span>.item.y = <span class="hljs-number">50</span>;
        <span class="hljs-built_in">this</span>.item.x = Phaser.Math.Between(<span class="hljs-number">50</span>, <span class="hljs-number">750</span>);
        <span class="hljs-built_in">this</span>.score += <span class="hljs-number">10</span>;
        <span class="hljs-built_in">this</span>.scoreText.setText(<span class="hljs-string">'Score: '</span> + <span class="hljs-built_in">this</span>.score);
}
</code></pre>
<p>And now every time you catch a block, the score updates:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734904845293/1131fadf-2780-4a74-a976-4d818e9746c0.png" alt /></p>
<h3 id="heading-2-increasing-difficulty"><strong>2. Increasing Difficulty</strong></h3>
<p>Let's make this more difficult and make the items fall faster as the score increases:</p>
<p>In our create function, add this:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">this</span>.itemSpeed = <span class="hljs-number">3</span>;
</code></pre>
<p>in our update() function, delete this:</p>
<pre><code class="lang-javascript">    <span class="hljs-comment">// Move falling item</span>
    <span class="hljs-built_in">this</span>.item.y += <span class="hljs-number">3</span>;
</code></pre>
<p>And replace it with this:</p>
<pre><code class="lang-javascript">  <span class="hljs-built_in">this</span>.item.y += <span class="hljs-built_in">this</span>.itemSpeed;

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.score &gt; <span class="hljs-number">0</span> &amp;&amp; <span class="hljs-built_in">this</span>.score % <span class="hljs-number">500</span> === <span class="hljs-number">0</span>) {
        <span class="hljs-built_in">this</span>.itemSpeed += <span class="hljs-number">0.5</span>;
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Going faster! Speed is now: '</span> + <span class="hljs-built_in">this</span>.itemSpeed);
    }
</code></pre>
<p>Now, every time you get 500 points, your speed increases, and it gets more difficult!</p>
<h3 id="heading-3-retro-feel-enhancements"><strong>3. Retro Feel Enhancements</strong></h3>
<p>So let's add some graphics to this to make it more exciting.</p>
<p>I just drew up these images, don't judge me.</p>
<p><em>You can</em> <a target="_blank" href="https://github.com/JeremyMorgan/Catch-The-Apples"><em>download the pail.png from here</em></a></p>
<p>in preload, add the following:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Load the player sprite</span>
<span class="hljs-built_in">this</span>.load.image(<span class="hljs-string">'player'</span>, <span class="hljs-string">'pail.png'</span>);
</code></pre>
<p>Then remove this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Player (Blue rectangle)</span>
<span class="hljs-built_in">this</span>.player = <span class="hljs-built_in">this</span>.add.rectangle(<span class="hljs-number">400</span>, <span class="hljs-number">550</span>, <span class="hljs-number">50</span>, <span class="hljs-number">50</span>, <span class="hljs-number">0x0000ff</span>);
</code></pre>
<p>And replace it with this:</p>
<pre><code class="lang-javascript">   <span class="hljs-comment">// Replace rectangle with sprite</span>
    <span class="hljs-built_in">this</span>.player = <span class="hljs-built_in">this</span>.add.sprite(<span class="hljs-number">400</span>, <span class="hljs-number">550</span>, <span class="hljs-string">'player'</span>);
    <span class="hljs-built_in">this</span>.player.setScale(<span class="hljs-number">1</span>); <span class="hljs-comment">// Adjust this value if needed to match desired size</span>
</code></pre>
<p>Let's turn the falling objects into apples.</p>
<p><em>You can</em> <a target="_blank" href="https://github.com/JeremyMorgan/Catch-The-Apples"><em>download the apple.png from here</em></a></p>
<p>In preload(), add:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">this</span>.load.image(<span class="hljs-string">'apple'</span>, <span class="hljs-string">'apple.png'</span>);  <span class="hljs-comment">// Loa</span>
</code></pre>
<p>In create(), delete this:</p>
<pre><code class="lang-javascript">    <span class="hljs-comment">// Falling item (Green rectangle)</span>
    <span class="hljs-built_in">this</span>.item = <span class="hljs-built_in">this</span>.add.rectangle(<span class="hljs-number">400</span>, <span class="hljs-number">50</span>, <span class="hljs-number">50</span>, <span class="hljs-number">50</span>, <span class="hljs-number">0x00ff00</span>);
</code></pre>
<p>and replace it with this:</p>
<pre><code class="lang-javascript">    <span class="hljs-comment">// Apple sprite</span>
    <span class="hljs-built_in">this</span>.item = <span class="hljs-built_in">this</span>.add.sprite(<span class="hljs-number">400</span>, <span class="hljs-number">50</span>, <span class="hljs-string">'apple'</span>);
    <span class="hljs-built_in">this</span>.item.setScale(<span class="hljs-number">1</span>); <span class="hljs-comment">// Adjust scale if needed</span>
</code></pre>
<p>And now you'll see a different look!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734904861042/849761ea-0298-40e3-a180-8719eb92e51c.png" alt /></p>
<p>Now we see an apple and a pail, but it's in a dark room. Let's enhance our look by adding in a background.</p>
<p><em>You can</em> <a target="_blank" href="https://github.com/JeremyMorgan/Catch-The-Apples"><em>download the background.png from here</em></a></p>
<p>In preload():</p>
<pre><code class="lang-javascript"> <span class="hljs-built_in">this</span>.load.image(<span class="hljs-string">'background'</span>, <span class="hljs-string">'background.png'</span>);  <span class="hljs-comment">// Load background image</span>
</code></pre>
<p>and in create():</p>
<pre><code class="lang-javascript">    <span class="hljs-comment">// Add background first so it appears behind other sprites</span>
    <span class="hljs-built_in">this</span>.add.image(<span class="hljs-number">400</span>, <span class="hljs-number">300</span>, <span class="hljs-string">'background'</span>);  <span class="hljs-comment">// Position at center of game (800/2, 600/2)</span>
</code></pre>
<p>And let's make the score black so we can see it:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">this</span>.scoreText = <span class="hljs-built_in">this</span>.add.text(<span class="hljs-number">16</span>, <span class="hljs-number">16</span>, <span class="hljs-string">'Score: 0'</span>, { <span class="hljs-attr">fontSize</span>: <span class="hljs-string">'24px'</span>, <span class="hljs-attr">fill</span>: <span class="hljs-string">'#fff'</span> });
</code></pre>
<p>Now save it and reload it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734904895553/58378674-d8f1-4a44-aea5-d9ed59fe877e.png" alt class="image--center mx-auto" /></p>
<p>Hey that looks awesome!</p>
<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>Congratulations! 🎉 You’ve built a working <em>"Catch the Items"</em> game using Phaser 3. Here’s <a target="_blank" href="https://jeremymorgan.itch.io/catch-the-apples">a playable version of it</a> as well.</p>
<h3 id="heading-next-steps-to-try-on-your-own"><strong>Next Steps to try on your own:</strong></h3>
<ul>
<li><p>Customize the shapes with images or sprites.</p>
</li>
<li><p>Add sound effects.</p>
</li>
<li><p>Experiment with game parameters like speed and item spawn rates.</p>
</li>
</ul>
<p>Keep practicing, and have fun creating your games. The possibilities are endless—go make something awesome! 🚀</p>
<p><em>Note: If you'd rather have a video tutorial, here it is:</em></p>
<iframe class="youtubevid" src="https://www.youtube.com/embed/rCgDvYNoaoc"></iframe>]]></content:encoded></item><item><title><![CDATA[How to Make a Retro 2D JavaScript Game Part 2]]></title><description><![CDATA[Let’s make this game interactive! We’ll add a player, movement controls, and falling items.
Note: If you'd rather have a video tutorial, here it is:


The full source code is here. Here’s a playable version of the final game.
1. Adding Placeholder Gr...]]></description><link>https://www.jeremymorgan.dev/how-to-make-a-retro-2d-javascript-game-part-2</link><guid isPermaLink="true">https://www.jeremymorgan.dev/how-to-make-a-retro-2d-javascript-game-part-2</guid><category><![CDATA[how to build games]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Games]]></category><category><![CDATA[how-to]]></category><category><![CDATA[phaser]]></category><category><![CDATA[2D Games]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[#beginners #learningtocode #100daysofcode]]></category><category><![CDATA[beginner]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Sun, 22 Dec 2024 21:56:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734904596905/89e83aff-e059-4ed4-846f-9707f29c0ff0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let’s make this game interactive! We’ll add a player, movement controls, and falling items.</p>
<p><em>Note: If you'd rather have a video tutorial, here it is:</em></p>
<iframe class="youtubevid" src="https://www.youtube.com/embed/gFrix6Bz-C0"></iframe>

<p>The <a target="_blank" href="https://github.com/JeremyMorgan/Catch-The-Apples"><strong>full source code is here</strong></a>. <a target="_blank" href="https://github.com/JeremyMorgan/Catch-The-Apples">Here’s a <strong>playable versi</strong></a><a target="_blank" href="https://jeremymorgan.itch.io/catch-the-apples"><strong>on</strong></a> <a target="_blank" href="https://jeremymorgan.itch.io/catch-the-apples">of the final gam</a>e.</p>
<h3 id="heading-1-adding-placeholder-graphics"><strong>1. Adding Placeholder Graphics</strong></h3>
<p>Update the <code>create</code> function to add shapes representing the player and falling items:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Player (Blue rectangle)</span>
    <span class="hljs-built_in">this</span>.player = <span class="hljs-built_in">this</span>.add.rectangle(<span class="hljs-number">400</span>, <span class="hljs-number">550</span>, <span class="hljs-number">50</span>, <span class="hljs-number">50</span>, <span class="hljs-number">0x0000ff</span>);

    <span class="hljs-comment">// Falling item (Green rectangle)</span>
    <span class="hljs-built_in">this</span>.item = <span class="hljs-built_in">this</span>.add.rectangle(<span class="hljs-number">400</span>, <span class="hljs-number">50</span>, <span class="hljs-number">50</span>, <span class="hljs-number">50</span>, <span class="hljs-number">0x00ff00</span>);

    <span class="hljs-comment">// Enable physics</span>
    <span class="hljs-built_in">this</span>.physics.add.existing(<span class="hljs-built_in">this</span>.player);
    <span class="hljs-built_in">this</span>.physics.add.existing(<span class="hljs-built_in">this</span>.item);

    <span class="hljs-comment">// Player controls</span>
    <span class="hljs-built_in">this</span>.cursors = <span class="hljs-built_in">this</span>.input.keyboard.createCursorKeys();
}
</code></pre>
<h3 id="heading-2-moving-the-player-left-and-right"><strong>2. Moving the Player Left and Right</strong></h3>
<p>Add movement logic to the <code>update</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Move player left</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.cursors.left.isDown) {
        <span class="hljs-built_in">this</span>.player.x -= <span class="hljs-number">5</span>;
    }
    <span class="hljs-comment">// Move player right</span>
    <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.cursors.right.isDown) {
        <span class="hljs-built_in">this</span>.player.x += <span class="hljs-number">5</span>;
    }
}
</code></pre>
<ul>
<li>The player can now move left and right using arrow keys!</li>
</ul>
<h3 id="heading-3-adding-simple-collision-logic"><strong>3. Adding Simple Collision Logic</strong></h3>
<p>We need to update our config:</p>
<p>change:</p>
<pre><code class="lang-javascript">   type: Phaser.AUTO, <span class="hljs-comment">// Auto-detect WebGL or Canvas</span>
   <span class="hljs-attr">width</span>: <span class="hljs-number">800</span>,        <span class="hljs-comment">// Game width</span>
   <span class="hljs-attr">height</span>: <span class="hljs-number">600</span>,       <span class="hljs-comment">// Game height</span>
</code></pre>
<p>To this, to add physics and gravity to our scene.</p>
<pre><code class="lang-javascript">type: Phaser.AUTO,
            <span class="hljs-attr">width</span>: <span class="hljs-number">800</span>,
            <span class="hljs-attr">height</span>: <span class="hljs-number">600</span>,
            <span class="hljs-attr">physics</span>: {
                <span class="hljs-attr">default</span>: <span class="hljs-string">'arcade'</span>,
                <span class="hljs-attr">arcade</span>: {
                    <span class="hljs-attr">gravity</span>: { <span class="hljs-attr">y</span>: <span class="hljs-number">0</span> }
                }
            },
</code></pre>
<p>Make the falling item reset its position when it reaches the bottom:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Move falling item</span>
    <span class="hljs-built_in">this</span>.item.y += <span class="hljs-number">3</span>;

    <span class="hljs-comment">// Reset item position</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.item.y &gt; <span class="hljs-number">600</span>) {
        <span class="hljs-built_in">this</span>.item.y = <span class="hljs-number">50</span>;
        <span class="hljs-built_in">this</span>.item.x = Phaser.Math.Between(<span class="hljs-number">50</span>, <span class="hljs-number">750</span>); <span class="hljs-comment">// Random x-position</span>
    }

    <span class="hljs-comment">// Check for overlap</span>
    <span class="hljs-keyword">if</span> (Phaser.Geom.Intersects.RectangleToRectangle(<span class="hljs-built_in">this</span>.player.getBounds(), <span class="hljs-built_in">this</span>.item.getBounds())) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Caught an item!'</span>);
        <span class="hljs-built_in">this</span>.item.y = <span class="hljs-number">50</span>;
        <span class="hljs-built_in">this</span>.item.x = Phaser.Math.Between(<span class="hljs-number">50</span>, <span class="hljs-number">750</span>);
    }
}
</code></pre>
<p>Now you'll see a screen that looks like this: and you should see green blocks falling. You can also move the player with your arrow keys:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734904382453/afa6187f-0bf4-4472-a530-0435b609592a.png" alt /></p>
<blockquote>
<p>📝 <strong>Recap</strong>: You added player movement, falling items, and basic collision detection. Your game is interactive! Now on to part 3</p>
</blockquote>
<p><em>Note: If you'd rather have a video tutorial,</em> <a target="_blank" href="https://www.youtube.com/watch?v=gFrix6Bz-C0"><em>it’s available here</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to Make a Retro 2D JavaScript Game Part 1]]></title><description><![CDATA[Welcome, aspiring game developers! 🚀 In this beginner-friendly guide, we’ll build a simple, retro-themed "Catch the Items" game using Phaser 3, a powerful JavaScript game development framework. This series of tutorials is designed for absolute begin...]]></description><link>https://www.jeremymorgan.dev/how-to-make-a-retro-2d-javascript-game-part-1</link><guid isPermaLink="true">https://www.jeremymorgan.dev/how-to-make-a-retro-2d-javascript-game-part-1</guid><category><![CDATA[how to build games]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[Games]]></category><category><![CDATA[how-to]]></category><category><![CDATA[phaser]]></category><category><![CDATA[2D Games]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[#beginners #learningtocode #100daysofcode]]></category><category><![CDATA[beginner]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Sun, 22 Dec 2024 21:49:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734903796173/c02eca55-febe-4555-9abe-62db97470a29.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome, aspiring game developers! 🚀 In this beginner-friendly guide, we’ll build a simple, retro-themed <em>"Catch the Items"</em> game using <strong>Phaser 3</strong>, a powerful JavaScript game development framework. This series of tutorials is designed for absolute beginners, so don’t worry if you’re new to coding or Phaser—you’re in good hands.</p>
<p><em>Note: If you'd rather have a video tutorial, here it is:</em></p>
<iframe class="youtubevid" src="https://www.youtube.com/embed/WkiZSpFJozM"></iframe>

<hr />
<p>The <a target="_blank" href="https://github.com/JeremyMorgan/Catch-The-Apples">full source code is here</a>. Here’s a <a target="_blank" href="https://jeremymorgan.itch.io/catch-the-apples">playable version</a> of the final game.</p>
<h2 id="heading-tutorial-1-project-setup-amp-basics"><strong>Tutorial 1: Project Setup &amp; Basics</strong></h2>
<p>Let’s start by setting up the environment, introducing key JavaScript concepts, and creating our first Phaser scene.</p>
<blockquote>
<p><em>Note:</em> In many of our tutorials we use Node/Vite to set up our games. But I want to keep this as simple as possible. To run this you can install the <a target="_blank" href="https://www.npmjs.com/package/serve">NPM package serve</a> or just open the document with Chrome.</p>
</blockquote>
<h3 id="heading-1-what-is-phaser-3"><strong>1. What is Phaser 3?</strong></h3>
<p>Phaser 3 is a popular <strong>2D game development framework</strong> for creating browser-based games using JavaScript. It’s beginner-friendly, flexible, and powerful—perfect for making retro-inspired games like this one!</p>
<hr />
<h3 id="heading-2-setting-up-your-development-environment"><strong>2. Setting Up Your Development Environment</strong></h3>
<p>To keep things simple, we’ll use Phaser via its <strong>CDN link</strong>. Here’s the setup:</p>
<ol>
<li><p><strong>Create a project folder</strong>:</p>
<ul>
<li><p>Make a new folder on your computer, e.g., <code>catch-game</code>.</p>
</li>
<li><p>Inside the folder, create a file named <code>index.html</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Add the basic HTML structure</strong>: Paste the following code into your <code>index.html</code> file:</p>
</li>
</ol>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Catch the Items<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
        <span class="hljs-comment">// Phaser Game Configuration</span>
        <span class="hljs-keyword">const</span> config = {
            <span class="hljs-attr">type</span>: Phaser.AUTO, <span class="hljs-comment">// Auto-detect WebGL or Canvas</span>
            <span class="hljs-attr">width</span>: <span class="hljs-number">800</span>,        <span class="hljs-comment">// Game width</span>
            <span class="hljs-attr">height</span>: <span class="hljs-number">600</span>,       <span class="hljs-comment">// Game height</span>
            <span class="hljs-attr">scene</span>: {
                <span class="hljs-attr">preload</span>: preload,
                <span class="hljs-attr">create</span>: create,
                <span class="hljs-attr">update</span>: update
            }
        };

        <span class="hljs-keyword">const</span> game = <span class="hljs-keyword">new</span> Phaser.Game(config);

        <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">preload</span>(<span class="hljs-params"></span>) </span>{
            <span class="hljs-comment">// Preload assets (none yet)</span>
        }

        <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"></span>) </span>{
            <span class="hljs-comment">// Add game objects here</span>
            <span class="hljs-built_in">this</span>.add.text(<span class="hljs-number">300</span>, <span class="hljs-number">250</span>, <span class="hljs-string">'Hello, Phaser!'</span>, { <span class="hljs-attr">fontSize</span>: <span class="hljs-string">'32px'</span>, <span class="hljs-attr">fill</span>: <span class="hljs-string">'#fff'</span> });
        }

        <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"></span>) </span>{
            <span class="hljs-comment">// Game loop (empty for now)</span>
        }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<ol start="3">
<li><p><strong>Run your game</strong>:</p>
<ul>
<li><p>Open the <code>index.html</code> file in any browser (Chrome is recommended).</p>
</li>
<li><p>You should see a simple canvas with the text <strong>"Hello, Phaser!"</strong>.</p>
</li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734903726567/9cd5fba5-d8f1-41cb-9de3-28aeee1980ac.png" alt /></p>
<blockquote>
<p>🎉 <strong>Congratulations</strong>! You’ve set up Phaser and displayed your first scene!</p>
</blockquote>
<hr />
<h3 id="heading-3-javascript-basics-for-phaser"><strong>3. JavaScript Basics for Phaser</strong></h3>
<p>Before diving deeper, let’s quickly review some JavaScript concepts:</p>
<ul>
<li><p><strong>Variables</strong>: Store data.</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">let</span> playerName = <span class="hljs-string">'PhaserHero'</span>;
</code></pre>
</li>
<li><p><strong>Functions</strong>: Reusable blocks of code.</p>
<pre><code class="lang-javascript">  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">greetPlayer</span>(<span class="hljs-params"></span>) </span>{
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Welcome to the game!'</span>);
  }
  greetPlayer();
</code></pre>
</li>
<li><p><strong>Objects</strong>: Collections of properties and methods.</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">let</span> player = {
      <span class="hljs-attr">name</span>: <span class="hljs-string">'Hero'</span>,
      <span class="hljs-attr">score</span>: <span class="hljs-number">0</span>,
      <span class="hljs-attr">jump</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Player jumps!'</span>);
      }
  };
  player.jump();
</code></pre>
</li>
</ul>
<p>You’ll use these concepts as you develop your game in Phaser.</p>
<hr />
<h3 id="heading-4-creating-a-minimal-phaser-scene"><strong>4. Creating a Minimal Phaser Scene</strong></h3>
<p>Modify the <code>create</code> function to display a basic game canvas and some text:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">this</span>.add.text(<span class="hljs-number">300</span>, <span class="hljs-number">250</span>, <span class="hljs-string">'Catch the Items!'</span>, { <span class="hljs-attr">fontSize</span>: <span class="hljs-string">'32px'</span>, <span class="hljs-attr">fill</span>: <span class="hljs-string">'#fff'</span> });
    <span class="hljs-built_in">this</span>.add.rectangle(<span class="hljs-number">400</span>, <span class="hljs-number">300</span>, <span class="hljs-number">50</span>, <span class="hljs-number">50</span>, <span class="hljs-number">0xff0000</span>); <span class="hljs-comment">// Red square</span>
}
</code></pre>
<ul>
<li>Reload the browser—you should see a red square in the center of the screen.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734903749862/6cf4e363-ac07-45a7-ae06-3fee530acb59.png" alt /></p>
<h3 id="heading-what-does-create-mean">What does create() mean?</h3>
<p>Phaser uses three core functions to manage the game lifecycle: preload, create, and update.</p>
<p>Let's break down what each function does:</p>
<ul>
<li><p><strong>preload()</strong>: This function is called before the game starts. It's where you load your game assets like images, sounds, and more.</p>
</li>
<li><p><strong>create()</strong>: This function is called after the assets are loaded. It's where you create your game objects, set up the game world, and define initial game settings.</p>
</li>
<li><p><strong>update()</strong>: This function is called repeatedly throughout the game loop. It's where you update the game state, handle user input, and make things happen in your game world.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734903763187/5831ee96-5eaf-4af4-adc4-a0701a3f5b5f.png" alt /></p>
<p>This diagram may not make sense now but it will as you get deeper into Phaser.</p>
<p>We'll be using these functions extensively as we build our games in this tutorial and future tutorials.</p>
<blockquote>
<p>📝 <strong>Recap</strong>: You’ve set up a Phaser game, learned some JavaScript basics, and displayed simple shapes on the canvas. Great start! Now on to Part 2!</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Getting Started with Python]]></title><description><![CDATA[Hey there, fellow geeks and future coders! Welcome to Part 1 of our series, "Learn Python"! If you've ever wanted to learn Python but felt overwhelmed by where to start, you’re in the right place. We’re going to break it down in easy, bite-sized chun...]]></description><link>https://www.jeremymorgan.dev/getting-started-with-python</link><guid isPermaLink="true">https://www.jeremymorgan.dev/getting-started-with-python</guid><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[python beginner]]></category><category><![CDATA[python projects]]></category><category><![CDATA[python tutorial]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Mon, 16 Dec 2024 18:54:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734375115349/a211a7da-f3ca-42e2-b397-3124cbc5f69c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey there, fellow geeks and future coders! Welcome to <strong>Part 1</strong> of our series, "Learn Python"! If you've ever wanted to learn Python but felt overwhelmed by where to start, you’re in the right place. We’re going to break it down in easy, bite-sized chunks, walking you through each concept step by step. So grab your coffee (or energy drink of choice), and let’s dive right in!</p>
<h2 id="heading-why-python">Why Python?</h2>
<p>First off, why should you learn Python? Well, Python is one of the easiest programming languages to get started with. It’s used everywhere—web development, data science, automation, artificial intelligence, you name it! Whether you’re automating boring tasks or building the next big app, Python has your back.</p>
<p>But the best part? <strong>Python is super beginner-friendly</strong>. Its syntax (fancy word for how code is written) is clean and straightforward, so you can focus more on <em>what</em> you want to do rather than <em>how</em> to do it.</p>
<p>Pretty cool, right?</p>
<h3 id="heading-what-can-you-build-with-python">What Can You Build with Python?</h3>
<ul>
<li><p>Websites (ever heard of Django or Flask?)</p>
</li>
<li><p>Automation scripts (goodbye repetitive tasks!)</p>
</li>
<li><p>Data analysis and machine learning models</p>
</li>
<li><p>Games (even 2D ones like the classics!)</p>
</li>
<li><p>IoT projects (you know, like controlling lights with a Raspberry Pi)</p>
</li>
</ul>
<p>Okay, enough hype—let’s get started!</p>
<h2 id="heading-step-1-setting-up-python">Step 1: Setting Up Python</h2>
<p>Before we can write any Python code, we need to set up our environment. Don’t worry, this is easier than it sounds.</p>
<h3 id="heading-install-python">Install Python</h3>
<p>First, you'll need to install Python if you don't have it already. Go to the official Python website <a target="_blank" href="http://python.org">python.org</a>, download the latest version, and follow the instructions.</p>
<p><strong>For Windows</strong>: During installation, make sure to check the box that says “Add Python to PATH.” This will let you run Python from anywhere on your system.</p>
<p><strong>For macOS/Linux</strong>: Python usually comes pre-installed, but if you need the latest version, you can install it via a package manager like Homebrew (<code>brew install python3</code>).</p>
<p>If you want, you can also <a target="_blank" href="https://www.jeremymorgan.com/tools/run-python/">run Python in a Web Browser</a> here.</p>
<h3 id="heading-verify-the-installation">Verify the Installation</h3>
<p>Once installed, open a terminal (or Command Prompt on Windows) and type:</p>
<pre><code class="lang-bash">python --version
</code></pre>
<p>If you see something like <code>Python 3.x.x</code>, congratulations! Python is ready to roll.</p>
<h3 id="heading-running-python-code">Running Python Code</h3>
<p>You can run Python code in two ways:</p>
<ol>
<li><p><strong>Interactive Mode</strong>: Just type <code>python</code> in your terminal, and you can start writing Python code line-by-line. This is great for quick tests.</p>
<p> <em>Demo idea:</em> Try it out right now! Type <code>python</code> in your terminal and then type:</p>
<pre><code class="lang-python"> print(<span class="hljs-string">"Testing in interactive mode!"</span>)
</code></pre>
<p> Hit enter, and you’ll see the result immediately. This is a fun way to experiment.</p>
</li>
<li><p><strong>Script Mode</strong>: Write your Python code in a <code>.py</code> file (like <a target="_blank" href="http://hello.py"><code>hello.py</code></a>), then run it by typing:</p>
<pre><code class="lang-bash"> python hello.py
</code></pre>
</li>
</ol>
<h2 id="heading-step-2-your-first-python-program">Step 2: Your First Python Program</h2>
<p>Alright, let’s write your very first Python program. We’re going to create the classic “Hello, World!” program.</p>
<h3 id="heading-write-your-code">Write Your Code</h3>
<p>Open your favorite text editor (VSCode, Sublime, or even Notepad), and type the following:</p>
<pre><code class="lang-python">print(<span class="hljs-string">"Hello, World!"</span>)
</code></pre>
<p>Save the file as <a target="_blank" href="http://hello.py"><code>hello.py</code></a>, then run it from the terminal:</p>
<pre><code class="lang-bash">python hello.py
</code></pre>
<p>Boom! You should see:</p>
<pre><code class="lang-python">Hello, World!
</code></pre>
<p>🎉 <strong>Congrats, you just wrote your first Python program!</strong> 🎉</p>
<h3 id="heading-make-it-interactive-optional-demo">Make It Interactive (Optional Demo)</h3>
<p>Want to take this a step further? Try prompting the user for input:</p>
<pre><code class="lang-python">name = input(<span class="hljs-string">"What's your name? "</span>)
print(<span class="hljs-string">"Nice to meet you, "</span> + name + <span class="hljs-string">"!"</span>)
</code></pre>
<p>Save it as <code>hello_</code><a target="_blank" href="http://interactive.py"><code>interactive.py</code></a> and run it. When prompted, type your name and see how Python responds! This little demo shows how Python can interact with users, making it feel more like a conversation and less like static code.</p>
<h2 id="heading-step-3-understanding-the-basics">Step 3: Understanding the Basics</h2>
<p>Let’s go over some fundamental concepts in Python. These are building blocks that we’ll use in the next parts of this series.</p>
<h3 id="heading-variables">Variables</h3>
<p>Think of variables as boxes where you can store information, like numbers or text.</p>
<pre><code class="lang-python"><span class="hljs-comment"># This is a variable storing a number</span>
age = <span class="hljs-number">30</span>

<span class="hljs-comment"># This is a variable storing a string (text)</span>
name = <span class="hljs-string">"Alice"</span>
</code></pre>
<p>You can use these variables later in your code:</p>
<pre><code class="lang-python">print(<span class="hljs-string">"My name is"</span>, name)
print(<span class="hljs-string">"I am"</span>, age, <span class="hljs-string">"years old"</span>)
</code></pre>
<h3 id="heading-data-types">Data Types</h3>
<p>Python has several data types you’ll use frequently:</p>
<ul>
<li><p><strong>Integers</strong>: Whole numbers (e.g., <code>42</code>)</p>
</li>
<li><p><strong>Floats</strong>: Decimal numbers (e.g., <code>3.14</code>)</p>
</li>
<li><p><strong>Strings</strong>: Text (e.g., <code>"Hello, World!"</code>)</p>
</li>
<li><p><strong>Booleans</strong>: <code>True</code> or <code>False</code></p>
</li>
</ul>
<p>You can even check the type of any variable with:</p>
<pre><code class="lang-python">print(type(age))  <span class="hljs-comment"># Outputs: &lt;class 'int'&gt;</span>
print(type(name)) <span class="hljs-comment"># Outputs: &lt;class 'str'&gt;</span>
</code></pre>
<h3 id="heading-comments">Comments</h3>
<p>Sometimes you need to add notes to your code. Python ignores comments, so they’re just for you (or anyone else reading the code).</p>
<pre><code class="lang-python"><span class="hljs-comment"># This is a comment. Python won't run this.</span>
</code></pre>
<h3 id="heading-math-operations">Math Operations</h3>
<p>Python can handle math like a calculator:</p>
<pre><code class="lang-python">x = <span class="hljs-number">10</span>
y = <span class="hljs-number">5</span>

print(x + y)  <span class="hljs-comment"># Addition</span>
print(x - y)  <span class="hljs-comment"># Subtraction</span>
print(x * y)  <span class="hljs-comment"># Multiplication</span>
print(x / y)  <span class="hljs-comment"># Division</span>
</code></pre>
<h3 id="heading-make-a-simple-calculator-demo">Make a Simple Calculator (Demo)</h3>
<p>Now that you know variables and math, let’s make a tiny calculator program:</p>
<pre><code class="lang-python">num1 = int(input(<span class="hljs-string">"Enter a number: "</span>))
num2 = int(input(<span class="hljs-string">"Enter another number: "</span>))
print(<span class="hljs-string">"Sum:"</span>, num1 + num2)
print(<span class="hljs-string">"Product:"</span>, num1 * num2)
</code></pre>
<p>Run this, and you can quickly see Python doing something practical—performing math operations on the fly!</p>
<h3 id="heading-a-quick-variable-experiment">A Quick Variable Experiment</h3>
<p>Try this short experiment to understand how strings can be combined:</p>
<pre><code class="lang-python">message = <span class="hljs-string">"Hello"</span>
message += <span class="hljs-string">", World!"</span>
print(message)
</code></pre>
<p>This shows you how variables can evolve as your program runs.</p>
<h2 id="heading-step-4-next-steps">Step 4: Next Steps</h2>
<p>Now that you've got Python set up and written your first program, you're ready to take the next step. In <strong>Part 2</strong> of this series, we’ll explore <strong>conditions and loops</strong>, where Python will start making decisions for you and performing repetitive tasks. It’s going to be fun!</p>
<h3 id="heading-practice-challenge">Practice Challenge</h3>
<p>Before we wrap up, try writing a Python script that does the following:</p>
<ul>
<li><p>Stores your name in a variable.</p>
</li>
<li><p>Prints a greeting like "Hello, [Your Name]!"</p>
</li>
<li><p>Multiplies two numbers and prints the result.</p>
</li>
</ul>
<p>Here’s a hint for the multiplication part:</p>
<pre><code class="lang-python">result = <span class="hljs-number">8</span> * <span class="hljs-number">7</span>
print(result)
</code></pre>
<p>Got it? Awesome! You’re on your way to becoming a Python pro. 🚀</p>
<p>See you in <strong>Part 2</strong>, where we’ll make Python even more powerful!</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[AI New Hotness December 13th 2024]]></title><description><![CDATA[Welcome to this week’s edition of the AI New Hotness Newsletter, where we talk about new and exciting stuff to happen in the world of Generative AI, particularly for software developers. It’s a fast moving, wacky world for sure.
Reminder: This is als...]]></description><link>https://www.jeremymorgan.dev/ai-new-hotness-december-13th-2024</link><guid isPermaLink="true">https://www.jeremymorgan.dev/ai-new-hotness-december-13th-2024</guid><category><![CDATA[AI]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[Apple]]></category><category><![CDATA[Apple Intelligence]]></category><category><![CDATA[AI News and Updates]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Sat, 14 Dec 2024 05:34:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734154344814/d2b061ba-d027-48e9-a96f-1034735fe876.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to this week’s edition of the AI New Hotness Newsletter, where we talk about new and exciting stuff to happen in the world of Generative AI, particularly for software developers. It’s a fast moving, wacky world for sure.</p>
<p><strong>Reminder:</strong> This is also <a target="_blank" href="https://www.linkedin.com/pulse/ai-new-hotness-december-13th-2024-jeremy-morgan-8c4kc/">available on LinkedIn</a> if you’d rather receive it there.</p>
<h2 id="heading-ai-in-the-news">AI in the News</h2>
<p><img src="https://www.jeremymorgan.com/images/newsletter/ai-news.png" alt="“AI in the News”" /></p>
<h3 id="heading-introducing-chatgpt-pro">Introducing ChatGPT Pro</h3>
<p><em>OpenAI releases ChatGPT Pro, a subscription service with powerful new features</em></p>
<p>OpenAI has released a new subscription service called ChatGPT Pro. This service provides unlimited access to OpenAI’s most advanced models, including o1, o1-mini, GPT-4o, and Advanced Voice. It also includes o1 pro mode, which uses more compute to provide more reliable and accurate answers to difficult questions. ChatGPT Pro is designed for researchers, engineers, and other professionals who need to use AI for complex tasks.</p>
<p><strong>Details:</strong></p>
<p>ChatGPT Pro provides unlimited access to OpenAI’s most advanced models, including o1, o1-mini, GPT-4o, and Advanced Voice. It also includes o1 pro mode, which uses more compute to provide more reliable and accurate answers to difficult questions. ChatGPT Pro is designed for researchers, engineers, and other professionals who need to use AI for complex tasks.</p>
<p><strong>Why it Matters:</strong></p>
<p>ChatGPT Pro is a powerful new tool that can be used for a variety of tasks, including writing, research, and development. With its unlimited access to OpenAI’s most advanced models, ChatGPT Pro can help you to be more productive and creative.</p>
<h3 id="heading-click-here-to-read-morehttpsopenaicomindexintroducing-chatgpt-pro"><a target="_blank" href="https://openai.com/index/introducing-chatgpt-pro/">Click here to read more</a></h3>
<hr />
<h3 id="heading-make-movies-with-text-this-ai-writes-videos-for-you">Make Movies with Text! This AI Writes Videos for You</h3>
<p>OpenAI Unveils Sora, a Powerful New Video Generation Model</p>
<p>OpenAI has released a new AI model called Sora that can create realistic videos from just a text description. This means you can create movies, explainer videos, or even home videos using just your imagination and some text!</p>
<p><strong>Details:</strong></p>
<p>Sora can generate videos in a variety of styles, from photorealistic to cartoonish. It can also be used to create videos with special effects or to animate existing images.</p>
<p><strong>Why it Matters:</strong></p>
<p>Sora is a powerful new tool that could revolutionize the way videos are created. It could make it possible for anyone to create professional-quality videos, even if they don’t have any filmmaking experience.</p>
<h3 id="heading-click-here-to-read-morehttpsopenaicomindexsora-is-here"><a target="_blank" href="https://openai.com/index/sora-is-here/">Click here to read more</a></h3>
<hr />
<h3 id="heading-unleash-your-creativity-with-amazon-nova-the-powerful-new-ai">Unleash Your Creativity with Amazon Nova, the Powerful New AI</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/www_aboutamazon_com_amazon-nova-artificial-intelligence-bedrock-aws.jpg" alt="Article Image" /></p>
<p><strong>Generate Stunning Videos and Images, and More!</strong></p>
<p>Do you ever feel limited by your imagination? Amazon Nova is here to help! This new generation of AI can be used to generate all sorts of creative content, from videos and images to who-knows-what-else. Amazon Nova is also being used to improve the customer shopping experience on Amazon. Keep reading to learn more about how Amazon Nova can help you!</p>
<p><strong>Details:</strong></p>
<ul>
<li><p>Amazon Nova is a new generation of foundation models that can be used to generate creative content.</p>
</li>
<li><p>Amazon Nova is also being used to improve the customer shopping experience on Amazon.</p>
</li>
</ul>
<p><strong>Why it Matters:</strong></p>
<p>Amazon Nova is a powerful new tool that can be used to generate creative content and improve the customer shopping experience. If you’re looking for a way to boost your creativity or make shopping on Amazon even easier, then Amazon Nova is definitely worth checking out.</p>
<h3 id="heading-click-here-to-read-morehttpswwwaboutamazoncomnewsawsamazon-nova-artificial-intelligence-bedrock-aws"><a target="_blank" href="https://www.aboutamazon.com/news/aws/amazon-nova-artificial-intelligence-bedrock-aws">Click here to read more</a></h3>
<hr />
<h3 id="heading-google-releases-revolutionary-ai-model-gemini-20">Google Releases Revolutionary AI Model, Gemini 2.0</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/blog_google_google-gemini-ai-update-december-2024.jpg" alt="Article Image" /></p>
<p>Google unveils its latest AI model, Gemini 2.0, promising to revolutionize the field of AI agent creation. Gemini 2.0 boasts advanced capabilities that push the boundaries of what’s possible in AI. But with great power comes great responsibility, and Google emphasizes its commitment to developing AI responsibly, with safety and security at the forefront.</p>
<p><strong>Details:</strong></p>
<ul>
<li><p>Gemini 2.0 is more capable than previous versions, with native image and audio output and tool use.</p>
</li>
<li><p>Gemini 2.0 Flash is available to developers and trusted testers, with wider availability planned for early next year.</p>
</li>
<li><p>Google is exploring agentic experiences with Gemini 2.0, including Project Astra, Project Mariner, and Jules.</p>
</li>
<li><p>Google is committed to building AI responsibly, with safety and security as key priorities.</p>
</li>
</ul>
<p><strong>Why it Matters:</strong></p>
<p>Gemini 2.0 has the potential to revolutionize various fields by enabling the creation of powerful and intelligent AI agents. Google’s commitment to responsible AI development ensures these advancements are made with safety and security in mind.</p>
<h3 id="heading-click-here-to-read-morehttpsbloggoogletechnologygoogle-deepmindgoogle-gemini-ai-update-december-2024"><a target="_blank" href="https://blog.google/technology/google-deepmind/google-gemini-ai-update-december-2024/">Click here to read more</a></h3>
<hr />
<h3 id="heading-apples-new-ai-can-turn-your-thoughts-into-emojis">Apple’s New AI Can Turn Your Thoughts Into Emojis!</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/www_apple_com_apple-intelligence-now-features-image-playground-genmoji-and-more.jpg" alt="Article Image" /></p>
<p>Tired of boring emojis? Apple Intelligence has just received a major upgrade, bringing you Image Playground and Genmoji to revolutionize the way you express yourself.</p>
<p>Image Playground lets you unleash your creativity and create unique images using your imagination. It’s like having a mini art studio right on your device!</p>
<p>Genmoji takes emojis to a whole new level. Generate custom emojis that perfectly capture your mood or personality.</p>
<p>Apple Intelligence is available now as a free software update, so you can start creating and expressing yourself in exciting new ways. But hurry, this update is only available for iPhone 16 and later, iPad with A17 Pro or M1 and later, and Mac with M1 and later!</p>
<p><strong>Why it Matters:</strong></p>
<p>Apple Intelligence’s new features offer a fun and creative way to express yourself through images and emojis. It’s a free update available for compatible devices, so there’s no reason not to try it out!</p>
<h3 id="heading-click-here-to-read-morehttpswwwapplecomnewsroom202412apple-intelligence-now-features-image-playground-genmoji-and-more"><a target="_blank" href="https://www.apple.com/newsroom/2024/12/apple-intelligence-now-features-image-playground-genmoji-and-more/">Click here to read more</a></h3>
<hr />
<h3 id="heading-apple-secretly-developing-ai-chip-to-rival-nvidia">Apple Secretly Developing AI Chip to Rival NVIDIA</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/finance_yahoo_com_apple-working-ai-chip-broadcom-141956432.jpg" alt="Article Image" /></p>
<p>Apple is reportedly working with Broadcom to develop its own AI chip, code-named Baltra, to power its AI services and reduce reliance on Nvidia. This move positions Apple alongside other tech giants like Google in the race to develop cutting-edge AI hardware.</p>
<p><strong>Details:</strong></p>
<ul>
<li><p>Apple is developing its first server chip specifically for AI processing.</p>
</li>
<li><p>This move aims to reduce reliance on Nvidia’s high-priced and often unavailable processors.</p>
</li>
<li><p>Baltra is expected to be ready for mass production by 2026.</p>
</li>
<li><p>Apple and Broadcom have a history of collaboration, including a recent multi-billion-dollar deal for 5G components.</p>
</li>
<li><p>This development highlights the growing importance of AI hardware and the increasing competition in the AI chip market.</p>
</li>
</ul>
<h3 id="heading-click-here-to-read-morehttpsfinanceyahoocomnewsapple-working-ai-chip-broadcom-141956432html"><a target="_blank" href="https://finance.yahoo.com/news/apple-working-ai-chip-broadcom-141956432.html">Click here to read more</a></h3>
<hr />
<h3 id="heading-talk-to-santa-and-see-him-lives">Talk to Santa AND See Him Live!s</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/www_youtube_com_watch.jpg" alt="Article Image" /></p>
<p>OpenAI just dropped a major update for the holiday season! You can now chat with Santa Claus in real-time using video calls and screen sharing. This new “Advanced Voice Mode” feature allows you to:</p>
<ul>
<li><p>Ask Santa anything: Get advice on beard care, hear about life at the North Pole, or have him tell you a story!</p>
</li>
<li><p>See Santa’s jolly face: Experience the magic of Christmas firsthand with live video chat.</p>
</li>
<li><p>Get help with tricky responses: Brainstorm the perfect reply to a friend’s Christmas message using screen sharing.</p>
</li>
<li><p>This feature is rolling out now:</p>
</li>
</ul>
<p>Available on mobile apps first, then desktop apps and web. Free for everyone to chat with Santa on the first try, even if you’ve used your voice limit. Full access to video and screen sharing for Plus, Pro, and Teams users (Europe rollout coming soon). Enterprise and EDU plans get access early next year.</p>
<h3 id="heading-click-here-to-read-morehttpswwwyoutubecomwatchvniqdnwlwyyqq"><a target="_blank" href="https://www.youtube.com/watch?v=NIQDnWlwYyQQ">Click here to read more</a></h3>
<hr />
<h3 id="heading-anthropics-new-ai-model-is-it-worth-the-hype-and-the-higher-price">Anthropic’s New AI Model: Is it Worth the Hype (and the Higher Price)?</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/techcrunch_com_anthropics-3-5-haiku-model-comes-to-claude-users.jpg" alt="Article Image" /></p>
<p>Claude 3.5 Haiku is here, but does it live up to the promise?</p>
<p>Anthropic has released Claude 3.5 Haiku, a powerful new AI model that boasts improved coding, data analysis, and content moderation capabilities. However, this upgrade comes with a higher price tag, sparking debate among developers.</p>
<p><strong>Details:</strong></p>
<p>Claude 3.5 Haiku surpasses its predecessor, 3 Opus, in key areas, offering enhanced performance in coding, data extraction, and content moderation.</p>
<p>It can generate longer pieces of text and boasts a more up-to-date knowledge base. However, 3.5 Haiku lacks image analysis capabilities, limiting its versatility compared to other Anthropic models. The model’s release was initially met with controversy due to an unexpected price increase.</p>
<h3 id="heading-click-here-to-read-morehttpstechcrunchcom20241212anthropics-3-5-haiku-model-comes-to-claude-users"><a target="_blank" href="https://techcrunch.com/2024/12/12/anthropics-3-5-haiku-model-comes-to-claude-users/">Click here to read more</a></h3>
<hr />
<h2 id="heading-ai-tools">AI Tools</h2>
<p><img src="https://www.jeremymorgan.com/images/newsletter/ai-tools.png" alt="“AI Tools”" /></p>
<h3 id="heading-create-engaging-video-training-content-in-minutes-with-lupo">Create Engaging Video Training Content in Minutes with Lupo</h3>
<p>Lupo is a platform that helps businesses create high-quality video training content quickly and easily. With Lupo, you can use Markdown and Marp to create courses, and even leverage their agency services if needed.</p>
<p><strong>Why you might need this tool:</strong></p>
<ul>
<li><p>Create high-quality video training content quickly and easily</p>
</li>
<li><p>Use Markdown and Marp to create courses</p>
</li>
<li><p>Leverage their agency services if needed</p>
</li>
</ul>
<h3 id="heading-click-here-to-read-morehttpslupoai"><a target="_blank" href="https://lupo.ai/">Click here to read more</a></h3>
<hr />
<h3 id="heading-want-to-build-ai-tools-like-a-pro-this-low-code-platform-makes-it-easy-even-without-coding-skills">Want to Build AI Tools Like a Pro? This Low-Code Platform Makes it Easy (Even Without Coding Skills!)</h3>
<p>AISmartCube is a low-code AI tool platform that allows users to build AI tools without any coding required. The platform provides access to global large models, various plugins, a shared knowledge base, and ready-to-use tools and assistants. It also offers flexible pricing with points and a free credit allowance.</p>
<h3 id="heading-click-here-to-read-morehttpsaismartcubecom"><a target="_blank" href="https://aismartcube.com/">Click here to read more</a></h3>
<hr />
<h3 id="heading-want-to-run-any-program-automatically">Want to run any program automatically?</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/pinokio_computer_.jpg" alt="Article Image" /></p>
<p>Pinokio is a browser that lets you install, run, and programmatically control any application, automatically. You can use Pinokio scripts, which are shared by the community, to automate tasks or create new functionalities.</p>
<p><strong>Why you might need this tool:</strong></p>
<p>With Pinokio, you can:</p>
<ul>
<li><p>Automate repetitive tasks</p>
</li>
<li><p>Create custom functionalities for your favorite applications</p>
</li>
<li><p>Be more productive by letting Pinokio handle the boring stuff</p>
</li>
</ul>
<h3 id="heading-click-here-to-read-morehttpspinokiocomputer"><a target="_blank" href="https://pinokio.computer/">Click here to read more</a></h3>
<hr />
<h3 id="heading-build-stunning-websites-in-3d-without-code-dora">Build Stunning Websites in 3D, Without Code - Dora!</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/www_dora_run_.jpg" alt="Article Image" /></p>
<p>Dora is a no-code platform that allows you to create 3D animated websites. With Dora, you can create beautiful and engaging websites without having to write any code.</p>
<p><strong>Why you might need this tool:</strong></p>
<p>Create stunning and unique websites that will stand out from the competition. No coding required, so even those with no coding experience can create professional websites. Large community of users to help you learn and grow.</p>
<h3 id="heading-click-here-to-read-morehttpswwwdorarun"><a target="_blank" href="https://www.dora.run/">Click here to read more</a></h3>
<hr />
<h3 id="heading-build-websites-and-landing-pages-in-minutes-no-coding-required">Build Websites and Landing Pages in Minutes - No Coding Required!</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/www_sitekick_ai_.jpg" alt="Article Image" /></p>
<p>Sitekick AI is an AI-powered tool that allows you to create landing pages and websites easily. It can generate text, images, and code for your website, so you don’t need any coding or design skills.</p>
<p><strong>Why you might need this tool:</strong></p>
<p><strong>Easy to use</strong>: Sitekick AI does not require any coding or design skills, so it is perfect for people who do not have any experience with web development. <strong>Affordable</strong>: Sitekick AI is affordable, and it has a 30-day money-back guarantee, so you can try it out risk-free. <strong>Saves time</strong>: Sitekick AI can help you create websites and landing pages quickly and easily, so you can save time and focus on other important tasks.</p>
<h3 id="heading-click-here-to-read-morehttpswwwsitekickai"><a target="_blank" href="https://www.sitekick.ai/">Click here to read more</a></h3>
<hr />
<h2 id="heading-learn-about-ai">Learn about AI</h2>
<p><img src="https://www.jeremymorgan.com/images/newsletter/learn-ai.png" alt="“Learn about AI”" /></p>
<h3 id="heading-learn-anything-faster-with-the-power-of-ai">Learn Anything Faster with the Power of AI</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/kodekloud_com_ai.jpg" alt="Article Image" /></p>
<p>KodeKloud has recently released an AI tutor that can personalize the training for you, providing real-time feedback and guides to craft a custom learning sequence just for you. Each task is validated for accuracy and it supports multiple languages.</p>
<p><strong>Personalized learning</strong>: The AI-powered learning suite tailors the learning experience to your individual needs and goals.</p>
<p><strong>Real-time feedback</strong>: The AI Assistant provides real-time feedback on your progress, so you can identify and address any areas of difficulty.</p>
<p><strong>Custom learning sequences</strong>: The AI Tutor creates a custom learning sequence for any tech topic you want to master.</p>
<p><strong>Accuracy validation</strong>: Each task is validated for accuracy, so you can be sure that you are learning the correct information.</p>
<p><strong>Multilingual support</strong>: The AI Assistant supports multiple languages, so you can learn in your preferred language.</p>
<h3 id="heading-click-here-to-read-morehttpskodekloudcomai"><a target="_blank" href="https://kodekloud.com/ai">Click here to read more</a></h3>
<hr />
<h3 id="heading-build-a-multimodal-ai-agent-with-gemini-20">Build a Multimodal AI Agent with Gemini 2.0</h3>
<p>In this tutorial, we’ll build a Multimodal AI Agent using Google’s Gemini 2.0 Flash model that can simultaneously analyze videos and conduct web searches. This powerful combination allows the agent to provide comprehensive responses by understanding both visual content and related web information.</p>
<h3 id="heading-click-here-to-read-morehttpswwwtheunwindaicompbuild-a-multimodal-ai-agent-with-gemini-2-0"><a target="_blank" href="https://www.theunwindai.com/p/build-a-multimodal-ai-agent-with-gemini-2-0">Click here to read more</a></h3>
<hr />
<h3 id="heading-learn-ai-assisted-development">Learn AI Assisted Development</h3>
<p><img src="https://www.jeremymorgan.com/images/newsletter/12-13-24/kodekloud_com_ai-assisted-development.jpg" alt="Article Image" /></p>
<p>A course built by yours truly, you can learn AI-assisted programming, project planning, backend and frontend development, plus documentation using tools like ChatGPT, GitHub Copilot, BlackboxAI, Tabnine, and Cursor</p>
<h3 id="heading-click-here-to-read-morehttpskodekloudcomcoursesai-assisted-development"><a target="_blank" href="https://kodekloud.com/courses/ai-assisted-development">Click here to read more</a></h3>
<hr />
<p>Thanks for reading!!</p>
<p>I started this newsletter to share all the cool stuff I’m finding in AI over time. Keep updated on the latest stuff with this newsletter. I’m glad you’re here.</p>
<p>Share this on your LinkedIn page if you think your friends might be interested in this stuff.</p>
<p>BTW Check out <a target="_blank" href="https://bit.ly/JMYouTubeLive">my YouTube Channel</a> for more cool stuff with Generative AI. Tips, tutorials, reviews of AI tools and more.</p>
]]></content:encoded></item><item><title><![CDATA[Python Basic Syntax and Indentation: The Complete Beginner's Guide]]></title><description><![CDATA[When you’re first learning to program, Python stands out for a special reason: it’s designed to be read almost like English. Unlike other programming languages that use lots of symbols and brackets, Python relies on simple, clean formatting that make...]]></description><link>https://www.jeremymorgan.dev/python-basic-syntax-and-indentation-the-complete-beginners-guide</link><guid isPermaLink="true">https://www.jeremymorgan.dev/python-basic-syntax-and-indentation-the-complete-beginners-guide</guid><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[python beginner]]></category><category><![CDATA[python projects]]></category><category><![CDATA[python programming]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Programming Tutorials]]></category><category><![CDATA[how-to]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Thu, 12 Dec 2024 07:44:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733989193407/1bb7a454-bc11-4bcf-86cc-d1110b37cdac.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When you’re first learning to program, Python stands out for a special reason: it’s designed to be read almost like English. Unlike other programming languages that use lots of symbols and brackets, Python relies on simple, clean formatting that makes your code look like a well-organized document.</p>
<p>Think of Python’s syntax like the grammar rules of a language. Just as English has rules about how to structure sentences to make meaning clear, Python has rules about how to write code so both humans and computers can understand it.</p>
<h2 id="heading-understanding-pythons-basic-syntax"><strong>Understanding Python’s Basic Syntax</strong></h2>
<h3 id="heading-the-building-blocks"><strong>The Building Blocks</strong></h3>
<p>Let’s start with the simplest elements of Python syntax:</p>
<pre><code class="lang-python"><span class="hljs-comment"># This is a comment - Python ignores anything after the '#' symbol</span>
student_name = <span class="hljs-string">"Alice"</span>    <span class="hljs-comment"># A variable holding text (string)</span>
student_age = <span class="hljs-number">15</span>         <span class="hljs-comment"># A variable holding a number (integer)</span>
<span class="hljs-comment"># Using variables in a sentence (string formatting)</span>
print(<span class="hljs-string">f"Hello, my name is <span class="hljs-subst">{student_name}</span> and I'm <span class="hljs-subst">{student_age}</span> years old."</span>)
</code></pre>
<p>In this example, we’re using several basic elements of Python:</p>
<ul>
<li><p>Comments (lines starting with #)</p>
</li>
<li><p>Variables (student_name and student_age)</p>
</li>
<li><p>String formatting (the f”…” syntax)</p>
</li>
<li><p>The print function</p>
</li>
</ul>
<h3 id="heading-basic-operations"><strong>Basic Operations</strong></h3>
<p>Python can perform calculations and comparisons just like a calculator:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Basic math operations</span>
total_score = <span class="hljs-number">95</span> + <span class="hljs-number">87</span>    <span class="hljs-comment"># Addition</span>
average = total_score / <span class="hljs-number">2</span> <span class="hljs-comment"># Division</span>
<span class="hljs-comment"># Comparisons</span>
<span class="hljs-keyword">if</span> student_age &gt;= <span class="hljs-number">15</span>:
    print(<span class="hljs-string">f"<span class="hljs-subst">{student_name}</span> can take advanced classes"</span>)
</code></pre>
<h2 id="heading-the-heart-of-python-understanding-indentation"><strong>The Heart of Python: Understanding Indentation</strong></h2>
<p>Here’s where Python gets truly unique: instead of using brackets or special symbols to group code together, Python uses indentation. This might seem strange at first, but it makes Python code exceptionally clear and readable.</p>
<h3 id="heading-how-indentation-creates-structure"><strong>How Indentation Creates Structure</strong></h3>
<p>Think of indentation like the way you might organize a detailed outline:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">make_sandwich</span>():</span>
    print(<span class="hljs-string">"1. Get two slices of bread"</span>)  <span class="hljs-comment"># First level</span>
    <span class="hljs-keyword">if</span> has_cheese:
        print(<span class="hljs-string">"2. Add cheese"</span>)           <span class="hljs-comment"># Second level</span>
        print(<span class="hljs-string">"3. Add tomatoes"</span>)         <span class="hljs-comment"># Still second level</span>
    <span class="hljs-keyword">else</span>:
        print(<span class="hljs-string">"2. Add butter"</span>)           <span class="hljs-comment"># Second level in else block</span>
    print(<span class="hljs-string">"4. Put the slices together"</span>)  <span class="hljs-comment"># Back to first level</span>
</code></pre>
<p>Each indented block tells Python “these lines belong together.” It’s like creating a sub-list in an outline — everything indented under “if has_cheese:” is part of that condition.</p>
<h3 id="heading-the-rules-of-indentation"><strong>The Rules of Indentation</strong></h3>
<p>Let’s look at the key rules for Python indentation:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_grade</span>(<span class="hljs-params">score</span>):</span>
    <span class="hljs-comment"># Rule 1: Use exactly 4 spaces for each indentation level</span>
    <span class="hljs-keyword">if</span> score &gt;= <span class="hljs-number">90</span>:
        print(<span class="hljs-string">"Excellent!"</span>)
        <span class="hljs-keyword">if</span> score == <span class="hljs-number">100</span>:
            print(<span class="hljs-string">"Perfect score!"</span>)
<span class="hljs-comment"># Rule 2: Aligned blocks work together</span>
    <span class="hljs-keyword">elif</span> score &gt;= <span class="hljs-number">80</span>:
        print(<span class="hljs-string">"Good job!"</span>)
        print(<span class="hljs-string">"Keep it up!"</span>)  <span class="hljs-comment"># This line is part of the elif block</span>
    <span class="hljs-comment"># Rule 3: Unindented lines end the block</span>
    print(<span class="hljs-string">"Processing complete"</span>)  <span class="hljs-comment"># This runs regardless of score</span>
</code></pre>
<h3 id="heading-nested-indentation-going-deeper"><strong>Nested Indentation: Going Deeper</strong></h3>
<p>As your programs get more complex, you’ll often need multiple levels of indentation:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_weather</span>(<span class="hljs-params">temperature, is_raining</span>):</span>
    <span class="hljs-comment"># First level: inside function</span>
    <span class="hljs-keyword">if</span> temperature &gt; <span class="hljs-number">70</span>:
        <span class="hljs-comment"># Second level: inside if</span>
        <span class="hljs-keyword">if</span> is_raining:
            <span class="hljs-comment"># Third level: nested condition</span>
            print(<span class="hljs-string">"It's warm but raining"</span>)
            print(<span class="hljs-string">"Take an umbrella"</span>)
        <span class="hljs-keyword">else</span>:
            print(<span class="hljs-string">"It's a warm, sunny day"</span>)
            print(<span class="hljs-string">"Perfect for outdoors"</span>)
    <span class="hljs-keyword">else</span>:
        print(<span class="hljs-string">"It's cool outside"</span>)
        print(<span class="hljs-string">"Take a jacket"</span>)
</code></pre>
<h2 id="heading-complex-structures-and-indentation"><strong>Complex Structures and Indentation</strong></h2>
<p>Let’s look at a more complex example that shows how indentation helps organize code:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_student_grades</span>(<span class="hljs-params">students</span>):</span>
    <span class="hljs-keyword">for</span> student <span class="hljs-keyword">in</span> students:            <span class="hljs-comment"># First level loop</span>
        print(<span class="hljs-string">f"Checking <span class="hljs-subst">{student[<span class="hljs-string">'name'</span>]}</span>'s grades..."</span>)
total = <span class="hljs-number">0</span>
        <span class="hljs-keyword">for</span> grade <span class="hljs-keyword">in</span> student[<span class="hljs-string">'grades'</span>]: <span class="hljs-comment"># Second level loop</span>
            <span class="hljs-keyword">if</span> grade &gt; <span class="hljs-number">90</span>:              <span class="hljs-comment"># Third level condition</span>
                print(<span class="hljs-string">"Outstanding!"</span>)
            total += grade
        average = total / len(student[<span class="hljs-string">'grades'</span>])
        <span class="hljs-comment"># Back to first loop level</span>
        <span class="hljs-keyword">if</span> average &gt;= <span class="hljs-number">90</span>:
            print(<span class="hljs-string">"Honor Roll"</span>)
            <span class="hljs-keyword">if</span> student[<span class="hljs-string">'attendance'</span>] &gt; <span class="hljs-number">95</span>:  <span class="hljs-comment"># Another level</span>
                print(<span class="hljs-string">"Perfect Attendance Award"</span>)
</code></pre>
<h2 id="heading-common-patterns-and-best-practices"><strong>Common Patterns and Best Practices</strong></h2>
<h3 id="heading-handling-multiple-conditions"><strong>Handling Multiple Conditions</strong></h3>
<pre><code class="lang-python"><span class="hljs-comment"># Good: Clear and easy to follow</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_eligibility</span>(<span class="hljs-params">age, grade, attendance</span>):</span>
    <span class="hljs-keyword">if</span> age &lt; <span class="hljs-number">18</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Too young"</span>
<span class="hljs-keyword">if</span> grade &lt; <span class="hljs-number">70</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Grades too low"</span>
    <span class="hljs-keyword">if</span> attendance &lt; <span class="hljs-number">80</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Attendance too low"</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Eligible"</span>
<span class="hljs-comment"># Avoid: Too many nested levels</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">check_eligibility_nested</span>(<span class="hljs-params">age, grade, attendance</span>):</span>
    <span class="hljs-keyword">if</span> age &gt;= <span class="hljs-number">18</span>:
        <span class="hljs-keyword">if</span> grade &gt;= <span class="hljs-number">70</span>:
            <span class="hljs-keyword">if</span> attendance &gt;= <span class="hljs-number">80</span>:
                <span class="hljs-keyword">return</span> <span class="hljs-string">"Eligible"</span>
            <span class="hljs-keyword">else</span>:
                <span class="hljs-keyword">return</span> <span class="hljs-string">"Attendance too low"</span>
        <span class="hljs-keyword">else</span>:
            <span class="hljs-keyword">return</span> <span class="hljs-string">"Grades too low"</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Too young"</span>
</code></pre>
<h3 id="heading-working-with-functions-and-classes"><strong>Working with Functions and Classes</strong></h3>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Student</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, name</span>):</span>
        self.name = name
        self.grades = []
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_grade</span>(<span class="hljs-params">self, grade</span>):</span>
        <span class="hljs-comment"># Notice the consistent indentation in methods</span>
        <span class="hljs-keyword">if</span> isinstance(grade, (int, float)):
            <span class="hljs-keyword">if</span> <span class="hljs-number">0</span> &lt;= grade &lt;= <span class="hljs-number">100</span>:
                self.grades.append(grade)
                print(<span class="hljs-string">f"Grade <span class="hljs-subst">{grade}</span> added"</span>)
            <span class="hljs-keyword">else</span>:
                print(<span class="hljs-string">"Grade must be between 0 and 100"</span>)
        <span class="hljs-keyword">else</span>:
            print(<span class="hljs-string">"Grade must be a number"</span>)
</code></pre>
<h2 id="heading-common-mistakes-and-how-to-fix-them"><strong>Common Mistakes and How to Fix Them</strong></h2>
<h3 id="heading-indentation-errors"><strong>Indentation Errors</strong></h3>
<pre><code class="lang-python"><span class="hljs-comment"># WRONG - Inconsistent indentation</span>
<span class="hljs-keyword">if</span> score &gt; <span class="hljs-number">90</span>:
print(<span class="hljs-string">"Great job!"</span>)    <span class="hljs-comment"># Error: no indentation</span>
    print(<span class="hljs-string">"Keep it up!"</span>)   <span class="hljs-comment"># Error: inconsistent indentation</span>
<span class="hljs-comment"># RIGHT - Proper indentation</span>
<span class="hljs-keyword">if</span> score &gt; <span class="hljs-number">90</span>:
    print(<span class="hljs-string">"Great job!"</span>)
    print(<span class="hljs-string">"Keep it up!"</span>)
</code></pre>
<h3 id="heading-mixing-tabs-and-spaces"><strong>Mixing Tabs and Spaces</strong></h3>
<pre><code class="lang-python"><span class="hljs-comment"># WRONG - Mixed tabs and spaces (don't do this!)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">calculate_average</span>(<span class="hljs-params">numbers</span>):</span>
    total = <span class="hljs-number">0</span>
    count = <span class="hljs-number">0</span>    <span class="hljs-comment"># This line uses a tab</span>
    <span class="hljs-keyword">for</span> num <span class="hljs-keyword">in</span> numbers:    <span class="hljs-comment"># This line uses spaces</span>
        total += num
</code></pre>
<h3 id="heading-practice-exercise-putting-it-all-together"><strong>Practice Exercise: Putting It All Together</strong></h3>
<p>Try writing this program to practice indentation and syntax:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">grade_assignment</span>(<span class="hljs-params">score, late_days</span>):</span>
    <span class="hljs-comment"># Start with the base score</span>
    final_score = score
<span class="hljs-comment"># Check if the assignment is late</span>
    <span class="hljs-keyword">if</span> late_days &gt; <span class="hljs-number">0</span>:
        <span class="hljs-keyword">if</span> late_days &lt;= <span class="hljs-number">5</span>:
            <span class="hljs-comment"># Deduct 2 points per late day</span>
            final_score -= (late_days * <span class="hljs-number">2</span>)
        <span class="hljs-keyword">else</span>:
            <span class="hljs-comment"># Maximum lateness penalty</span>
            final_score -= <span class="hljs-number">10</span>
    <span class="hljs-comment"># Ensure score doesn't go below 0</span>
    <span class="hljs-keyword">if</span> final_score &lt; <span class="hljs-number">0</span>:
        final_score = <span class="hljs-number">0</span>
    <span class="hljs-comment"># Determine letter grade</span>
    <span class="hljs-keyword">if</span> final_score &gt;= <span class="hljs-number">90</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"A"</span>, final_score
    <span class="hljs-keyword">elif</span> final_score &gt;= <span class="hljs-number">80</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"B"</span>, final_score
    <span class="hljs-keyword">elif</span> final_score &gt;= <span class="hljs-number">70</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"C"</span>, final_score
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"F"</span>, final_score
<span class="hljs-comment"># Test the function</span>
score = <span class="hljs-number">95</span>
late_days = <span class="hljs-number">2</span>
letter_grade, final_score = grade_assignment(score, late_days)
print(<span class="hljs-string">f"Original Score: <span class="hljs-subst">{score}</span>"</span>)
print(<span class="hljs-string">f"Late Days: <span class="hljs-subst">{late_days}</span>"</span>)
print(<span class="hljs-string">f"Final Score: <span class="hljs-subst">{final_score}</span>"</span>)
print(<span class="hljs-string">f"Letter Grade: <span class="hljs-subst">{letter_grade}</span>"</span>)
</code></pre>
<h2 id="heading-key-takeaways"><strong>Key Takeaways</strong></h2>
<ol>
<li><p>Python uses indentation to understand code structure</p>
</li>
<li><p>Always use 4 spaces for each level of indentation</p>
</li>
<li><p>Be consistent with your indentation throughout your code</p>
</li>
<li><p>Simpler, flatter code structure is usually better than deeply nested code</p>
</li>
<li><p>Proper indentation makes code more readable and helps prevent errors</p>
</li>
</ol>
<h2 id="heading-next-steps"><strong>Next Steps</strong></h2>
<p>Now that you understand Python’s basic syntax and indentation:</p>
<ul>
<li><p>Practice writing simple programs focusing on proper indentation</p>
</li>
<li><p>Learn about different data types (strings, numbers, lists)</p>
</li>
<li><p>Explore functions and classes</p>
</li>
<li><p>Study loops and control structures</p>
</li>
<li><p>Start working with Python modules and libraries</p>
</li>
</ul>
<p>Remember: Good indentation habits form the foundation of becoming a skilled Python programmer. Take your time to master these concepts, and the rest will follow naturally!</p>
]]></content:encoded></item><item><title><![CDATA[Python Syntax and Variables]]></title><description><![CDATA[Hey there, Python enthusiasts! If you’re diving into the world of Python or brushing up your skills, mastering Python’s syntax and variables is a fantastic place to start. Python is known for its simplicity and readability, making it a top choice for...]]></description><link>https://www.jeremymorgan.dev/python-syntax-and-variables</link><guid isPermaLink="true">https://www.jeremymorgan.dev/python-syntax-and-variables</guid><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[python beginner]]></category><category><![CDATA[Python]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[how-to]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Tue, 03 Dec 2024 19:29:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733254088766/8ddd7a9d-d5f8-44c0-bcd2-36bad16f0242.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey there, Python enthusiasts! If you’re diving into the world of Python or brushing up your skills, mastering Python’s syntax and variables is a fantastic place to start. Python is known for its simplicity and readability, making it a top choice for developers of all levels. In this guide, we’ll unravel the basics of Python syntax and variables with plenty of practical examples and best practices. So, grab a coffee (or your favorite beverage) and let’s dive in!</p>
<hr />
<h2 id="heading-why-this-matters">Why This Matters</h2>
<p>First things first—why should we care about syntax and variables in Python? Here’s the deal:</p>
<ul>
<li><p><strong>Readability</strong>: Python’s clean, intuitive syntax means less time decoding code and more time solving problems.</p>
</li>
<li><p><strong>Efficiency</strong>: Proper use of variables keeps your code efficient and streamlined.</p>
</li>
<li><p><strong>Debugging</strong>: A solid grasp of syntax helps you pinpoint errors faster than a debugger.</p>
</li>
<li><p><strong>Scalability</strong>: Writing clear, organized code ensures that your projects can grow without turning into a tangled mess.</p>
</li>
</ul>
<p>Convinced? Great. Let’s start with the basics.</p>
<hr />
<h2 id="heading-python-syntax-basics">Python Syntax Basics</h2>
<h3 id="heading-indentation-pythons-secret-sauce">Indentation: Python’s Secret Sauce</h3>
<p>In Python, indentation isn’t just for looks—it’s how you define blocks of code. Forget braces (<code>{}</code>) and semicolons—just align your code with consistent spacing.</p>
<p>Here’s an example:</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> <span class="hljs-literal">True</span>:
    print(<span class="hljs-string">"Hello, Python!"</span>)
</code></pre>
<p>That’s it. The <code>print</code> statement is indented to show it belongs to the <code>if</code> block. Forget to indent, or mix spaces and tabs, and Python will call you out with a syntax error.</p>
<h3 id="heading-comments-talk-to-your-future-self">Comments: Talk to Your Future Self</h3>
<p>Comments in your code are lifesavers when you revisit it months (or years) later. Python supports:</p>
<ul>
<li><p><strong>Single-line comments</strong>: Start with <code>#</code>.</p>
</li>
<li><p><strong>Multi-line comments</strong>: Enclose with triple quotes (<code>'''</code> or <code>"""</code>).</p>
</li>
</ul>
<p>Here’s how:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Single-line comment</span>
<span class="hljs-string">"""
Multi-line comment
spanning several lines.
"""</span>
</code></pre>
<h3 id="heading-python-is-case-sensitive">Python is Case-Sensitive</h3>
<p>Python distinguishes between <code>Variable</code>, <code>variable</code>, and <code>VARIABLE</code>. Keep this in mind to avoid pesky bugs.</p>
<hr />
<h2 id="heading-variables-in-python">Variables in Python</h2>
<h3 id="heading-what-are-variables">What Are Variables?</h3>
<p>Think of variables as labeled storage containers for your data. Python is dynamically typed, so you don’t need to declare types upfront. Here’s a quick example:</p>
<pre><code class="lang-python">x = <span class="hljs-number">10</span>  <span class="hljs-comment"># Integer</span>
y = <span class="hljs-number">3.14</span>  <span class="hljs-comment"># Float</span>
z = <span class="hljs-string">"Hello, World!"</span>  <span class="hljs-comment"># String</span>
</code></pre>
<h3 id="heading-naming-variables">Naming Variables</h3>
<p>To keep your code clean and readable, follow these rules:</p>
<ul>
<li><p><strong>Rules</strong>:</p>
<ul>
<li><p>Start with a letter or underscore, not a number.</p>
</li>
<li><p>Use only letters, numbers, and underscores—no spaces or special characters.</p>
</li>
<li><p>Avoid Python keywords like <code>if</code>, <code>class</code>, or <code>def</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Conventions</strong>:</p>
<ul>
<li><p>Use <strong>snake_case</strong> (e.g., <code>user_name</code>).</p>
</li>
<li><p>Choose meaningful names—<code>score</code> is better than <code>s</code>.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-assigning-values">Assigning Values</h3>
<p>Assigning values is as simple as:</p>
<pre><code class="lang-python">a, b, c = <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>  <span class="hljs-comment"># Multiple assignments</span>
</code></pre>
<hr />
<h2 id="heading-common-python-data-types">Common Python Data Types</h2>
<p>Here’s a rundown of Python’s built-in data types:</p>
<ul>
<li><p><strong>Numeric</strong>:</p>
<ul>
<li><p><code>int</code>: Whole numbers (e.g., <code>42</code>)</p>
</li>
<li><p><code>float</code>: Decimal numbers (e.g., <code>3.14</code>)</p>
</li>
</ul>
</li>
<li><p><strong>Strings</strong>: Enclosed in single, double, or triple quotes:</p>
<pre><code class="lang-python">  greeting = <span class="hljs-string">"Hello, Python!"</span>
</code></pre>
</li>
<li><p><strong>Booleans</strong>: <code>True</code> or <code>False</code></p>
<pre><code class="lang-python">  is_active = <span class="hljs-literal">True</span>
</code></pre>
</li>
<li><p><strong>Lists</strong>: Ordered, mutable collections:</p>
<pre><code class="lang-python">  fruits = [<span class="hljs-string">"apple"</span>, <span class="hljs-string">"banana"</span>, <span class="hljs-string">"cherry"</span>]
</code></pre>
</li>
<li><p><strong>Dictionaries</strong>: Key-value pairs:</p>
<pre><code class="lang-python">  person = {<span class="hljs-string">"name"</span>: <span class="hljs-string">"Alice"</span>, <span class="hljs-string">"age"</span>: <span class="hljs-number">25</span>}
</code></pre>
</li>
</ul>
<hr />
<h2 id="heading-performing-operations-with-variables">Performing Operations with Variables</h2>
<h3 id="heading-arithmetic">Arithmetic</h3>
<p>Python handles math like a champ:</p>
<pre><code class="lang-python">x = <span class="hljs-number">10</span>
y = <span class="hljs-number">3</span>

print(x + y)  <span class="hljs-comment"># Addition</span>
print(x - y)  <span class="hljs-comment"># Subtraction</span>
print(x * y)  <span class="hljs-comment"># Multiplication</span>
print(x / y)  <span class="hljs-comment"># Division</span>
</code></pre>
<h3 id="heading-strings">Strings</h3>
<p>You can concatenate or repeat strings easily:</p>
<pre><code class="lang-python">name = <span class="hljs-string">"Alice"</span>
print(name + <span class="hljs-string">" Smith"</span>)  <span class="hljs-comment"># Alice Smith</span>
print(name * <span class="hljs-number">3</span>)  <span class="hljs-comment"># AliceAliceAlice</span>
</code></pre>
<h3 id="heading-logical-operations">Logical Operations</h3>
<p>Logical operators (<code>and</code>, <code>or</code>, <code>not</code>) are super handy:</p>
<pre><code class="lang-python">x = <span class="hljs-literal">True</span>
y = <span class="hljs-literal">False</span>

print(x <span class="hljs-keyword">and</span> y)  <span class="hljs-comment"># False</span>
print(x <span class="hljs-keyword">or</span> y)  <span class="hljs-comment"># True</span>
print(<span class="hljs-keyword">not</span> x)  <span class="hljs-comment"># False</span>
</code></pre>
<hr />
<h2 id="heading-best-practices">Best Practices</h2>
<p>Write clean, efficient Python by following these tips:</p>
<ul>
<li><p><strong>Descriptive Names</strong>: Use meaningful variable names.</p>
</li>
<li><p><strong>DRY Principle</strong>: Don’t Repeat Yourself—reuse your code.</p>
</li>
<li><p><strong>Follow PEP 8</strong>: Stick to Python’s style guide.</p>
</li>
<li><p><strong>Comment Smartly</strong>: Explain <em>why</em>, not <em>what</em>.</p>
</li>
<li><p><strong>Avoid Globals</strong>: Keep variables local to their functions when possible.</p>
</li>
</ul>
<hr />
<h2 id="heading-common-pitfalls-and-how-to-avoid-them">Common Pitfalls (And How to Avoid Them)</h2>
<ol>
<li><p><strong>Indentation Errors</strong>: Stick to spaces or tabs (not both), and use four spaces per level.</p>
</li>
<li><p><strong>Scope Issues</strong>: Know the difference between local and global variables.</p>
</li>
<li><p><strong>Type Mismatches</strong>: Python doesn’t mix types:</p>
<pre><code class="lang-python"> x = <span class="hljs-string">"5"</span>
 y = <span class="hljs-number">10</span>
 print(x + y)  <span class="hljs-comment"># TypeError</span>
</code></pre>
</li>
</ol>
<hr />
<h2 id="heading-faq">FAQ</h2>
<p><strong>Q: What’s the difference between variables and constants?</strong></p>
<p>Variables can change; constants stay fixed. Use all caps to indicate constants (e.g., <code>PI = 3.14</code>).</p>
<p><strong>Q: How can I check a variable’s type?</strong></p>
<p>Use <code>type()</code>:</p>
<pre><code class="lang-python">x = <span class="hljs-number">10</span>
print(type(x))  <span class="hljs-comment"># &lt;class 'int'&gt;</span>
</code></pre>
<p><strong>Q: Can I change a variable’s type?</strong></p>
<p>Sure can! Python allows dynamic typing:</p>
<pre><code class="lang-python">x = <span class="hljs-number">10</span>
x = <span class="hljs-string">"Now a string"</span>
</code></pre>
<hr />
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Mastering Python syntax and variables is your gateway to writing cleaner, more effective code. With practice, these basics will become second nature. In our next article, we’ll dive into conditional statements (<code>if</code>/<code>else</code>) and explore how they can help you write more dynamic and responsive code.</p>
]]></content:encoded></item><item><title><![CDATA[The Ultimate Guide to Running Local LLMs on Your Mac]]></title><description><![CDATA[Hello Friends! Want to run LLM (large language models) locally on your Mac? Here's your guide! We'll explore three powerful tools for running LLMs directly on your Mac without relying on cloud services or expensive subscriptions.
Whether you are a be...]]></description><link>https://www.jeremymorgan.dev/the-ultimate-guide-to-running-local-llms-on-your-mac</link><guid isPermaLink="true">https://www.jeremymorgan.dev/the-ultimate-guide-to-running-local-llms-on-your-mac</guid><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[llm]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[chatbot]]></category><category><![CDATA[Local LLM]]></category><category><![CDATA[ollama]]></category><category><![CDATA[lmstudio]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[tutorials]]></category><category><![CDATA[#howtos]]></category><category><![CDATA[macOS]]></category><category><![CDATA[apple silicon]]></category><category><![CDATA[large language models]]></category><category><![CDATA[huggingface]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Thu, 17 Oct 2024 17:33:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729186248882/5226778a-c75c-496b-9dd0-9d3ddbd485fd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello Friends! Want to run LLM (large language models) locally on your Mac? Here's your guide! We'll explore three powerful tools for running LLMs directly on your Mac without relying on cloud services or expensive subscriptions.</p>
<p>Whether you are a beginner or an experienced developer, you'll be up and running in no time. This is a great way to evaluate different open-source models or create a sandbox to write AI applications on your own machine.</p>
<p>We'll go from easy to use to a solution that requires programming.</p>
<h2 id="heading-products-were-using">Products we're using:</h2>
<ol>
<li><p><a class="post-section-overview" href="#lm-studio-user-friendly-ai-for-everyone">LM Studio: User-Friendly AI for Everyone</a></p>
</li>
<li><p><a class="post-section-overview" href="#Ollama-efficient-and-developer-friendly">Ollama: Efficient and Developer-Friendly</a></p>
</li>
<li><p><a class="post-section-overview" href="#hugging-face-transformers-advanced-model-access">Hugging Face Transformers: Advanced Model Access</a></p>
</li>
</ol>
<p>If you'd rather watch a video of this tutorial, here it is:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=e5iaYkSNrhY">https://www.youtube.com/watch?v=e5iaYkSNrhY</a></div>
<p> </p>
<h2 id="heading-1-lm-studio-user-friendly-ai-for-everyone">1. LM Studio: User-Friendly AI for Everyone</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185798612/f454f823-b121-410b-8052-e34158c2366c.webp" alt /></p>
<p>LM Studio is an excellent starting point for both beginners and experts. It provides an intuitive interface for exploring and using various AI models.</p>
<h3 id="heading-getting-started-with-lm-studio">Getting Started with LM Studio</h3>
<ol>
<li><p>Visit <a target="_blank" href="https://lmstudio.ai">lmstudio.ai</a> and download the appropriate version for your Mac.</p>
</li>
<li><p>Install LM Studio by dragging the downloaded file into your Applications folder.</p>
</li>
<li><p>Launch LM Studio and accept any security prompts.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185812561/e25bf60d-5f95-4c8e-87bd-e897c5aadc46.webp" alt /></p>
<h3 id="heading-exploring-models">Exploring Models</h3>
<ol>
<li>In the main interface, click "Load a model" or select a model from the "New and Noteworthy" list.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185823221/d761cf3a-48b5-4c89-8a9d-53744afab82c.webp" alt /></p>
<ol start="2">
<li><p>we'll use the "llama2 3B" model for this tutorial. Click on it to download.</p>
</li>
<li><p>Once downloaded, click "Load model" to activate it.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185833708/3350fd51-e791-41db-8c40-39eada2d1f46.webp" alt /></p>
<h3 id="heading-using-the-chat-interface">Using the Chat Interface</h3>
<ol>
<li><p>With the model loaded, you can start interacting with it in the chat interface.</p>
</li>
<li><p>Try asking a question like "Tell me a funny joke about Python."</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185842754/3ef5b3f9-c910-4d07-aa15-2dbffbc38541.webp" alt /></p>
<ol start="3">
<li>Observe the model's response and the performance metrics (tokens per second, context usage).</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185853962/9743de8c-949e-4d24-9290-410e51c7ca01.webp" alt /></p>
<h3 id="heading-utilizing-the-api-server">Utilizing the API Server</h3>
<p>LM Studio also provides an OpenAI-compatible API server, making it easy to integrate with your applications:</p>
<ol>
<li><p>Click on the server icon in the left sidebar.</p>
</li>
<li><p>Start the server by clicking the "Start Server" button. [6]</p>
</li>
<li><p>Copy the provided server address (usually <code>http://localhost:1234</code>).</p>
</li>
</ol>
<p>You can see a set of endpoints available:</p>
<pre><code class="lang-plaintext">GET http://localhost:1234/v1/models
POST http://localhost:1234/v1/chat/completions
POST http://localhost:1234/v1/completions
POST http://localhost:1234/v1/embeddings
</code></pre>
<p>You can now use this address to send requests to the model using tools like Postman or your own code. Here's an example using Postman:</p>
<ol>
<li><p>Create a new POST request to <code>http://localhost:1234/v1/chat/completions</code>.</p>
</li>
<li><p>Set the body to raw JSON with the following content:</p>
</li>
</ol>
<pre><code class="lang-json">{
        <span class="hljs-attr">"model"</span>: <span class="hljs-string">"lmstudio-community/Qwen2.5-14B-Instruct-GGUF/Qwen2.5-14B-Instruct-Q4_K_M.gguf"</span>,
        <span class="hljs-attr">"messages"</span>: [
            {
                <span class="hljs-attr">"role"</span>: <span class="hljs-string">"system"</span>,
                <span class="hljs-attr">"content"</span>: <span class="hljs-string">"You are a helpful jokester who knows a lot about Python"</span>
            },
            {
                <span class="hljs-attr">"role"</span>: <span class="hljs-string">"user"</span>,
                <span class="hljs-attr">"content"</span>: <span class="hljs-string">"Tell me a funny Python joke."</span>
            }
        ],
        <span class="hljs-attr">"response_format"</span>: {
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"json_schema"</span>,
            <span class="hljs-attr">"json_schema"</span>: {
                <span class="hljs-attr">"name"</span>: <span class="hljs-string">"joke_response"</span>,
                <span class="hljs-attr">"strict"</span>: <span class="hljs-string">"true"</span>,
                <span class="hljs-attr">"schema"</span>: {
                    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"object"</span>,
                    <span class="hljs-attr">"properties"</span>: {
                        <span class="hljs-attr">"joke"</span>: {
                            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"string"</span>
                        }
                    },
                    <span class="hljs-attr">"required"</span>: [
                        <span class="hljs-string">"joke"</span>
                    ]
                }
            }
        },
        <span class="hljs-attr">"temperature"</span>: <span class="hljs-number">0.7</span>,
        <span class="hljs-attr">"max_tokens"</span>: <span class="hljs-number">50</span>,
        <span class="hljs-attr">"stream"</span>: <span class="hljs-literal">false</span>
    }
</code></pre>
<ol start="3">
<li>Send the request and observe the model's response.</li>
</ol>
<p>LM Studio is perfect for quickly testing different models and integrating them into your projects with minimal setup.</p>
<h2 id="heading-2-ollama-efficient-and-developer-friendly">2. Ollama: Efficient and Developer-Friendly</h2>
<p>Ollama is a lightweight and powerful tool for deploying LLMs, which is ideal for developers who prefer working from the command line.</p>
<h3 id="heading-installing-ollama">Installing Ollama</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185879057/81499b4f-2392-43e1-8f11-29907722d534.webp" alt /></p>
<ol>
<li><p>Visit the Ollama website and download the Mac version.</p>
</li>
<li><p>Install Ollama by dragging the downloaded file into your Applications folder.</p>
</li>
<li><p>Launch Ollama and accept any security prompts.</p>
</li>
</ol>
<h3 id="heading-using-ollama-from-the-terminal">Using Ollama from the Terminal</h3>
<ol>
<li><p>Open a terminal window.</p>
</li>
<li><p>List available models by running: <code>Ollama list</code></p>
</li>
<li><p>To download and run a model, use: <code>Ollama run &lt;model-name&gt;</code>    For example: <code>Ollama run qwen2.5-14b</code></p>
</li>
<li><p>Once the model is loaded, you can interact directly with it in the terminal.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185898550/7634dce8-4a48-4818-b28a-445da444ec13.webp" alt /></p>
<h3 id="heading-ollama-commands-and-features">Ollama Commands and Features</h3>
<ul>
<li><p>Use <code>/?</code> to see available commands within a model session.</p>
</li>
<li><p>Exit a model session with <code>/bye</code>.</p>
</li>
<li><p>Run models with verbose output using <code>--verbose</code> flag.</p>
</li>
</ul>
<h3 id="heading-using-ollamas-api">Using Ollama's API</h3>
<p>Ollama also provides an API for integration with your applications:</p>
<ol>
<li><p>Ensure Ollama is running (you'll see the icon in your menu bar).</p>
</li>
<li><p>Send POST requests to <code>http://localhost:11434/api/generate</code>.</p>
</li>
</ol>
<p>Example using Postman:</p>
<pre><code class="lang-json">{
      <span class="hljs-attr">"model"</span>: <span class="hljs-string">"qwen2.5:14b"</span>,
      <span class="hljs-attr">"prompt"</span>: <span class="hljs-string">"Tell me a funny joke about Python"</span>,
      <span class="hljs-attr">"stream"</span>: <span class="hljs-literal">false</span>
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185910643/7c789ec1-f5fe-419e-94d2-377915748285.webp" alt /></p>
<h3 id="heading-ollama-python-library">Ollama Python Library</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185920604/da06d712-7016-4569-a192-731aa7e57b9f.webp" alt /></p>
<p>For Python developers, Ollama offers a convenient library:</p>
<ol>
<li><p>Install the library: <code>pip install ollama</code></p>
</li>
<li><p>Use it in your Python scripts:</p>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> ollama
response = ollama.chat(model=<span class="hljs-string">'qwen2.5:14b'</span>, messages=[
  {
    <span class="hljs-string">'role'</span>: <span class="hljs-string">'user'</span>,
    <span class="hljs-string">'content'</span>: <span class="hljs-string">'Tell me a funny joke about Golang!'</span>,
  },
])
print(response[<span class="hljs-string">'message'</span>][<span class="hljs-string">'content'</span>])
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185930442/2aee8e02-0309-4957-8b1f-6d75d2ce4629.webp" alt /></p>
<p>Ollama provides a great balance between ease of use and flexibility, making it an excellent choice for developers building AI-powered applications.</p>
<h2 id="heading-3-hugging-face-transformers-advanced-model-access">3. Hugging Face Transformers: Advanced Model Access</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185941012/99a88d2e-aa21-429e-b0ff-0d9f60542d60.webp" alt /></p>
<p>Hugging Face Transformers is a powerful library that gives you access to many models and more control over their usage.</p>
<h3 id="heading-setting-up-hugging-face-transformers">Setting Up Hugging Face Transformers</h3>
<ol>
<li>Create a new Python virtual environment:</li>
</ol>
<pre><code class="lang-bash">python -m venv env    <span class="hljs-built_in">source</span> env/bin/activate
</code></pre>
<ol start="2">
<li>Install required libraries:</li>
</ol>
<pre><code class="lang-bash">pip install torch transformers accelerate
</code></pre>
<ol start="3">
<li>Install Hugging Face CLI:</li>
</ol>
<pre><code class="lang-bash">pip install -U huggingface_hub[cli]`
</code></pre>
<ol start="4">
<li>Log in to Hugging Face:</li>
</ol>
<pre><code class="lang-bash">huggingface-cli login
</code></pre>
<p>(You'll need to create a <a target="_blank" href="https://huggingface.co/docs/hub/en/security-tokens">user access token</a> on the Hugging Face website)</p>
<h3 id="heading-using-a-model-with-transformers">Using a Model with Transformers</h3>
<p>Here's a simple example using the LLaMA 3.2 3B model:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> torch
<span class="hljs-keyword">from</span> transformers <span class="hljs-keyword">import</span> pipeline

model_id = <span class="hljs-string">"meta-llama/Llama-3.2-3B-Instruct"</span>
pipe = pipeline(
    <span class="hljs-string">"text-generation"</span>,
    model=model_id,
    torch_dtype=torch.bfloat16,
    device_map=<span class="hljs-string">"auto"</span>,
)
messages = [
    {<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"You are a pirate chatbot who always responds in pirate speak!"</span>},
    {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"Who are you?"</span>},
]
outputs = pipe(
    messages,
    max_new_tokens=<span class="hljs-number">256</span>,
)
print(outputs[<span class="hljs-number">0</span>][<span class="hljs-string">"generated_text"</span>][<span class="hljs-number">-1</span>])
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185966469/d374263b-2406-4749-a522-8c61838be389.webp" alt /></p>
<h3 id="heading-advantages-of-hugging-face-transformers">Advantages of Hugging Face Transformers</h3>
<ol>
<li><p>Access to a vast library of models</p>
</li>
<li><p>Fine-grained control over model parameters</p>
</li>
<li><p>Ability to fine-tune models for specific tasks</p>
</li>
<li><p>Integration with popular deep learning frameworks</p>
</li>
</ol>
<p>While Hugging Face Transformers requires more coding knowledge, it offers unparalleled flexibility and access to cutting-edge AI models.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729185982831/c8c4d33b-e61d-44d2-b163-2fca3a58410c.webp" alt /></p>
<p>We've explored three powerful tools for running AI models locally on your Mac:</p>
<ol>
<li><p>LM Studio: Perfect for beginners and quick experimentation</p>
</li>
<li><p>Ollama: Ideal for developers who prefer command-line interfaces and simple API integration</p>
</li>
<li><p>Hugging Face Transformers: Best for advanced users who need access to a wide range of models and fine-grained control</p>
</li>
</ol>
<p>Each tool has its strengths, and the choice depends on your specific needs and technical expertise. By running these models locally, you gain more control over your AI applications, ensure data privacy, and avoid the costs associated with cloud-based services.</p>
<p>Consider your Mac's specifications when working with larger models, as they can be resource-intensive. Start with smaller models and work up as you become more familiar with the tools and your hardware capabilities.</p>
<p>Happy coding, and enjoy exploring the world of local AI on your Mac!</p>
<p>If you have any questions or feedback feel free to <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">reach out</a> or leave a comment below.</p>
]]></content:encoded></item><item><title><![CDATA[The Top 10 Conversational AI Models for 2024]]></title><description><![CDATA[Like you, I use Generative AI nearly every single day. One common request I get is when folks have wet their feet with ChatGPT and ask me “What else is out there?” I send them a list of links and a little blurb about the sites. I decided to share tha...]]></description><link>https://www.jeremymorgan.dev/the-top-10-conversational-ai-models-for-2024</link><guid isPermaLink="true">https://www.jeremymorgan.dev/the-top-10-conversational-ai-models-for-2024</guid><category><![CDATA[generative ai]]></category><category><![CDATA[AI]]></category><category><![CDATA[aitools]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[chatbot]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[copilot]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Fri, 30 Aug 2024 20:24:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1725049363983/8865d963-df2c-424d-b9d5-ae977e202dc0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Like you, I use Generative AI nearly every single day. One common request I get is when folks have wet their feet with ChatGPT and ask me “What else is out there?” I send them a list of links and a little blurb about the sites. I decided to share that list and insights with you.</p>
<p>Bookmark this page because it will be updated frequently. I’ll also do this with some other tools I use (image generation, code generation, video, etc.).</p>
<p>Am I missing anything? Feel free to <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">reach out to me</a>!</p>
<p>Here are the top conversational AI tools out there right now.</p>
<h1 id="heading-perplexity"><strong>Perplexity</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*9BCOASn1fHy6Of40k5CSKQ.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://www.perplexity.ai/">https://www.perplexity.ai/</a></p>
</li>
<li><p><strong>Title:</strong> Perplexity AI</p>
</li>
<li><p><strong>Description:</strong> Perplexity — Where Knowledge Begins</p>
</li>
<li><p><strong>Why it’s awesome:</strong> It pulls from different sources to verify the information presented. Very magic.</p>
</li>
</ul>
<h1 id="heading-claude"><strong>Claude</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*eMGJLZ72TX3jVYMlG9En4Q.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://claude.ai/">https://claude.ai</a></p>
</li>
<li><p><strong>Title:</strong> Claude</p>
</li>
<li><p><strong>Description:</strong> Talk with Claude, an AI assistant from Anthropic</p>
</li>
<li><p><strong>Why it’s awesome:</strong> Excels in writing, and analysys.</p>
</li>
</ul>
<h1 id="heading-globe-explorer"><strong>Globe Explorer</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*Wg-65zbLYR5fS7oP9NsvNw.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://explorer.globe.engineer/">https://explorer.globe.engineer/</a></p>
</li>
<li><p><strong>Title:</strong> Globe Explorer</p>
</li>
<li><p><strong>Description:</strong> No description found</p>
</li>
<li><p><strong>Why it’s awesome:</strong> It pulls from many different sources to really deep dive into a subject.</p>
</li>
</ul>
<h1 id="heading-poe"><strong>Poe</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*c1Un0HUrgi3o6h4noKz4DQ.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://poe.com/">https://poe.com/</a></p>
</li>
<li><p><strong>Title:</strong> Poe — Fast, Helpful AI Chat</p>
</li>
<li><p><strong>Description:</strong> Poe lets you ask questions, get instant answers, and have back-and-forth conversations with AI. It gives access to GPT-4, GPT-3.5-turbo, Claude from Anthropic, and various other bots.</p>
</li>
<li><p><strong>Why it’s awesome:</strong> There are so many connections to other services, and the interface is very cohesive.</p>
</li>
</ul>
<h1 id="heading-grok"><strong>Grok</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*V8UO79xc6GB60hPgfI8jfg.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://x.com/i/grok">https://x.com/i/grok</a></p>
</li>
<li><p><strong>Title:</strong> x.com</p>
</li>
<li><p><strong>Description:</strong> AI Assistant, powered by X</p>
</li>
<li><p><strong>Why it’s awesome:</strong> It uses X as an additional source, and it is very up to date.</p>
</li>
</ul>
<h1 id="heading-gemini"><strong>Gemini</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*mavxLZwjxDllSsWMUALvIw.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://gemini.google.com/app">https://gemini.google.com/app</a></p>
</li>
<li><p><strong>Title:</strong> Gemini — chat to supercharge your ideas</p>
</li>
<li><p><strong>Description:</strong> Bard is now Gemini. Get help with writing, planning, learning, and more from Google AI.</p>
</li>
<li><p><strong>Why it’s awesome:</strong> Backed by Google, exciting and factual results.</p>
</li>
</ul>
<h1 id="heading-groq"><strong>Groq</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*k_BIsp2iJdwb6T3Kdg3aeQ.jpeg" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://groq.com/">https://groq.com/</a></p>
</li>
<li><p><strong>Title:</strong> Groq is Fast AI Inference</p>
</li>
<li><p><strong>Description:</strong> The LPU™ Inference Engine by Groq is a hardware and software platform that delivers exceptional compute speed, quality, and energy efficiency. Groq provides cloud and on-prem solutions at scale for AI applications.</p>
</li>
<li><p><strong>Why it’s awesome:</strong> Screaming fast and smart, great for applications.</p>
</li>
</ul>
<h1 id="heading-chatgpt"><strong>ChatGPT</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*m_4mFOpX0tgGsCfBLryg5A.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://openai.com/chatgpt/">https://openai.com/chatgpt/</a></p>
</li>
<li><p><strong>Title:</strong> ChatGPT by OpenAI</p>
</li>
<li><p><strong>Description:</strong> A conversational AI system that listens, learns, and challenges</p>
</li>
<li><p><strong>Why it’s awesome:</strong> It’s the OG, and Custom GPTs are awesome</p>
</li>
</ul>
<h1 id="heading-hugging-face-chat-models"><strong>Hugging Face Chat Models</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/0*NJgcpGX8gU2XGQvs.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://huggingface.co/chat/models">https://huggingface.co/chat/models</a></p>
</li>
<li><p><strong>Title:</strong> HuggingChat — Models</p>
</li>
<li><p><strong>Description:</strong> The first open source alternative to ChatGPT.</p>
</li>
<li><p><strong>Why it’s awesome:</strong> A wide variety of models to choose from</p>
</li>
</ul>
<h1 id="heading-bing-ai-copilot"><strong>Bing AI (Copilot)</strong></h1>
<p><img src="https://miro.medium.com/v2/resize:fit:945/1*jBa9BeOS1az33LpAt50MZg.png" alt /></p>
<ul>
<li><p><strong>URL:</strong> <a target="_blank" href="https://www.bing.com/chat">https://www.bing.com/chat</a></p>
</li>
<li><p><strong>Title:</strong> Microsoft Copilot in Bing</p>
</li>
<li><p><strong>Description:</strong> Chat, get answers, create amazing content, and discover information effortlessly with Bing’s AI-powered chat. Transform the way you search and get answers with Microsoft Copilot in Bing.</p>
</li>
<li><p><strong>Why it’s awesome:</strong> Fast and great for programming/coding stuff.</p>
</li>
</ul>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>This is the list of Conversational AI models I use constantly. I will update this as I go. If you can think of one I’m missing out on, <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">Contact me!</a></p>
<p>A<strong><em>lso if you’re into Computer Vision and other cool AI tech, you should subscribe to my</em></strong> <a target="_blank" href="https://mailchi.mp/4961a415a64f/learn-ai-newsletter"><strong><em>AI Architect Newsletter</em></strong></a> <strong><em>to keep up with the latest stuff!</em></strong></p>
]]></content:encoded></item><item><title><![CDATA[The Complete Guide to Image Compression with OpenCV]]></title><description><![CDATA[Image compression is a critical technology in computer vision that allows us to store and transmit images more efficiently while maintaining visual quality. Ideally, we’d love to have small files with the best quality. However, we must make the trade...]]></description><link>https://www.jeremymorgan.dev/the-complete-guide-to-image-compression-with-opencv</link><guid isPermaLink="true">https://www.jeremymorgan.dev/the-complete-guide-to-image-compression-with-opencv</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Computer Vision]]></category><category><![CDATA[opencv]]></category><category><![CDATA[opencv-python]]></category><category><![CDATA[image processing]]></category><category><![CDATA[Python]]></category><category><![CDATA[python beginner]]></category><category><![CDATA[python projects]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[tutorials]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Tue, 20 Aug 2024 04:39:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128561552/ac34af23-b95d-4db9-b048-a795e41cc067.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Image compression is a critical technology in computer vision that allows us to store and transmit images more efficiently while maintaining visual quality. Ideally, we’d love to have small files with the best quality. However, we must make the tradeoff and decide which is more important.</p>
<p>This tutorial will help you learn about image compression with OpenCV, covering theory and practical applications. By the end, you’ll understand how to compress photos successfully for computer vision projects (or any other projects you might have).</p>
<h2 id="heading-what-is-image-compression">What is Image Compression?</h2>
<p>Image compression is reducing an image’s file size while maintaining an acceptable level of visual quality. There are two main types of compression:</p>
<ol>
<li><p><strong>Lossless compression:</strong> Preserves all original data, allowing exact image reconstruction.</p>
</li>
<li><p><strong>Lossy compression:</strong> Discards some data to achieve smaller file sizes, potentially reducing image quality.</p>
</li>
</ol>
<h3 id="heading-why-compress-images">Why Compress Images?</h3>
<p>If “disk space is cheap,” as we often hear, then why compress images at all? At a small scale, image compression doesn’t matter much, but at a large scale, it’s crucial.</p>
<p>For instance, if you have a few images on your hard drive, you can compress them and save a few megabytes of data. This is not much of an impact when hard drives are measured in Terabytes. But what if you had 100,000 images on your hard drive? Some basic compression saves real time and money. From a performance perspective, it’s the same. If you have a website with a lot of images and 10,000 people visit your website a day, compression matters.</p>
<p>Here’s why we do it:</p>
<ul>
<li><p><strong>Reduced storage requirements</strong>: Store more images in the same space</p>
</li>
<li><p><strong>Faster transmission</strong>: Ideal for web applications and bandwidth-constrained scenarios</p>
</li>
<li><p><strong>Improved processing speed</strong>: Smaller images are quicker to load and process</p>
</li>
</ul>
<h2 id="heading-theory-behind-image-compression">Theory Behind Image Compression</h2>
<p>Image compression techniques exploit two types of redundancies:</p>
<ol>
<li><p><strong>Spatial redundancy</strong>: Correlation between neighboring pixels</p>
</li>
<li><p><strong>Color redundancy</strong>: Similarity of color values in adjacent regions</p>
</li>
</ol>
<p><strong>Spatial redundancy</strong> takes advantage of the fact that neighboring pixels tend to have similar values in most natural images. This creates smooth transitions. Many photos “look real” because there is a natural flow from one area to the other. When the neighboring pixels have wildly different values, you get “noisy” images. Pixels changed to make those transitions less “smooth” by grouping pixels into a single color, making the image smaller.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128180075/ad66353a-fdd8-42e7-bd95-83a63fc413ed.webp" alt class="image--center mx-auto" /></p>
<p><strong>Color redundancy</strong>, on the other hand, focuses on how adjacent areas in an image often share similar colors. Think of a blue sky or a green field—large portions of the image might have very similar color values. They can also be grouped together and made into a single color to save space.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128201050/a5bc6556-b752-4701-8816-0697039952cc.webp" alt class="image--center mx-auto" /></p>
<p>OpenCV offers solid tools for working with these ideas. Using spatial redundancy, OpenCV’s <code>cv2.inpaint()</code> function, for example, fills in missing or damaged areas of a picture using information from nearby pixels. OpenCV lets developers use <code>cv2.cvtColor()</code> to translate images between several color spaces regarding color redundancy. This can be somewhat helpful as a preprocessing step in many compression techniques since some color spaces are more effective than others in encoding particular kinds of images.</p>
<p>We’ll test out some of this theory now. Let’s play with it.</p>
<h2 id="heading-hands-on-image-compression">Hands on Image Compression</h2>
<p>Let’s explore how to compress images using OpenCV’s Python bindings. Write out this code or copy it:<br />(you can also <a target="_blank" href="https://github.com/JeremyMorgan/OpenCV_Compress_Image_Tutorial">download the source code here</a>)</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">compress_image</span>(<span class="hljs-params">image_path, quality=<span class="hljs-number">90</span></span>):</span>
    <span class="hljs-comment"># Read the image</span>
 img = cv2.imread(image_path)

    <span class="hljs-comment"># Encode the image with JPEG compression</span>
 encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
 _, encoded_img = cv2.imencode(<span class="hljs-string">'.jpg'</span>, img, encode_param)

    <span class="hljs-comment"># Decode the compressed image</span>
 decoded_img = cv2.imdecode(encoded_img, cv2.IMREAD_COLOR)

    <span class="hljs-keyword">return</span> decoded_img

<span class="hljs-comment"># Example usage</span>
original_img = cv2.imread(<span class="hljs-string">'original_image.jpg'</span>)
compressed_img = compress_image(<span class="hljs-string">'original_image.jpg'</span>, quality=<span class="hljs-number">50</span>)

<span class="hljs-comment"># Display results</span>
cv2.imshow(<span class="hljs-string">'Original'</span>, original_img)
cv2.imshow(<span class="hljs-string">'Compressed'</span>, compressed_img)
cv2.waitKey(<span class="hljs-number">0</span>)
cv2.destroyAllWindows()
</code></pre>
<p>This example contains a <code>compress_image</code> function that takes two parameters:</p>
<ul>
<li><p>Image path (where the image is located)</p>
</li>
<li><p>Quality (the quality of the image desired)</p>
</li>
</ul>
<p>Then, we’ll load the original image into <code>original_img</code>. We then compress that same image by 50% and load it into a new instance, <code>compressed_image</code>.</p>
<p>Then we’ll show the original and compressed images so you can view them side by side.</p>
<p>We then calculate and display the compression ratio.</p>
<p>This example demonstrates how to compress an image using JPEG compression in OpenCV. The <code>quality</code> parameter controls file size and image quality tradeoff.</p>
<p>Let’s run it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128230359/929e5828-b8d9-4b5b-a358-e36303796624.webp" alt class="image--center mx-auto" /></p>
<p>While initially looking at the images, you see little difference. However, zooming in shows you the difference in the quality:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128253097/77a3ea3c-c8a6-4808-8b86-dae99f15ed87.webp" alt class="image--center mx-auto" /></p>
<p>And after closing the windows and looking at the files, we can see the file was reduced in size dramatically:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128273465/2d4c3d32-a2b7-4815-8acf-db86d8495127.webp" alt class="image--center mx-auto" /></p>
<p>Also, if we take it down further, we can change our quality to 10%</p>
<pre><code class="lang-plaintext">compressed_img = compress_image('sampleimage.jpg', quality=10)
</code></pre>
<p>And the results are much more drastic:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128296834/83ac7ac9-0ab0-476d-95a3-3d978c207cf5.webp" alt class="image--center mx-auto" /></p>
<p>And the file size results are more drastic as well:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128316420/59f100e6-24a1-4db0-9231-5a93eb284a9e.webp" alt class="image--center mx-auto" /></p>
<p>You can adjust these parameters quite easily and achieve the desired balance between quality and file size.</p>
<h2 id="heading-evaluating-compression-quality">Evaluating Compression Quality</h2>
<p>To assess the impact of compression, we can use metrics like:</p>
<ol>
<li><strong>Mean Squared Error (MSE)</strong></li>
</ol>
<p>Mean Squared Error (MSE) measures how different two images are from each other. When you compress an image, MSE helps you determine how much the compressed image has changed compared to the original.</p>
<p>It does this by sampling the differences between the colors of corresponding pixels in the two images, squaring those differences, and averaging them. The result is a single number: a lower MSE means the compressed image is closer to the original. In comparison, a higher MSE means there’s a more noticeable loss of quality.</p>
<p>Here’s some Python code to measure that:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">calculate_mse</span>(<span class="hljs-params">img1, img2</span>):</span>
    <span class="hljs-keyword">return</span> np.mean((img1 - img2) ** <span class="hljs-number">2</span>)

mse = calculate_mse(original_img, compressed_img)
print(<span class="hljs-string">f"Mean Squared Error: <span class="hljs-subst">{mse:<span class="hljs-number">.2</span>f}</span>"</span>)
</code></pre>
<p>Here’s what our demo image compression looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128337705/ce5460d7-9ca6-48d1-89b9-74ebf8e616e9.webp" alt class="image--center mx-auto" /></p>
<ol>
<li><strong>Peak Signal-to-Noise Ratio (PSNR)</strong></li>
</ol>
<p>Peak Signal-to-Noise Ratio (PSNR) is a measure that shows how much an image’s quality has degraded after compression. This is often visible with your eyes, but it assigns a set value. It compares the original image to the compressed one and expresses the difference as a ratio.</p>
<p>A higher PSNR value means the compressed image is closer in quality to the original, indicating less loss of quality. A lower PSNR means more visible degradation. PSNR is often used alongside MSE, with PSNR providing an easier-to-interpret scale where higher is better.</p>
<p>Here is some Python code that measures that:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">calculate_psnr</span>(<span class="hljs-params">img1, img2</span>):</span>
 mse = calculate_mse(img1, img2)
    <span class="hljs-keyword">if</span> mse == <span class="hljs-number">0</span>:
        <span class="hljs-keyword">return</span> float(<span class="hljs-string">'inf'</span>)
 max_pixel = <span class="hljs-number">255.0</span>
    <span class="hljs-keyword">return</span> <span class="hljs-number">20</span> * np.log10(max_pixel / np.sqrt(mse))

psnr = calculate_psnr(original_img, compressed_img)
print(<span class="hljs-string">f"PSNR: <span class="hljs-subst">{psnr:<span class="hljs-number">.2</span>f}</span> dB"</span>)
</code></pre>
<p>Here’s what our demo image compression looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128363995/979e35c7-5c1d-468c-8a99-a48580f6d466.webp" alt class="image--center mx-auto" /></p>
<p>“Eyeballing” your images after compression to determine quality is fine; however, at a large scale, having scripts do this is a much easier way to set standards and ensure the images follow them.</p>
<p>Let’s look at a couple other techniques:</p>
<h2 id="heading-advanced-compression-techniques">Advanced Compression Techniques</h2>
<p>For more advanced compression, OpenCV supports various algorithms:</p>
<ol>
<li><strong>PNG Compression</strong>:</li>
</ol>
<p>You can convert your images to PNG format, which has many advantages. Use the following line of code, and you can set your compression from 0 to 9, depending on your needs. 0 means no compression whatsoever, and 9 is maximum. Keep in mind that PNGs are a “lossless” format, so even at maximum compression, the image should remain intact. The big trade-off is file size and compression time.</p>
<p>Here is the code to use PNG compression with OpenCV:</p>
<pre><code class="lang-python">cv2.imwrite(<span class="hljs-string">'compressed.png'</span>, img, [cv2.IMWRITE_PNG_COMPRESSION, <span class="hljs-number">9</span>])
</code></pre>
<p>And here is our result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128386171/ef336b61-e53e-4f62-9fb2-4a77e1cd3a26.webp" alt class="image--center mx-auto" /></p>
<p><em>Note: You may notice sometimes that PNG files are actually larger in size, depending on the image.</em></p>
<ol>
<li><strong>WebP Compression</strong>:</li>
</ol>
<p>You can also convert your images to .webp format. This is a newer method of compression that’s gaining in popularity. I have been using this compression on the images on my blog for years.</p>
<p>In the following code, we can write our image to a webp file and set the compression level from 0 to 100. It’s the opposite of PNG’s scale because 0, because we’re setting <em>quality</em> instead of <em>compression</em>. This small distinction matters, because a setting of 0 is the lowest possible quality, with a small file size and significant loss. 100 is the highest quality, which means large files with the best image quality.</p>
<p>Here’s the Python code to make that happen:</p>
<pre><code class="lang-python">cv2.imwrite(<span class="hljs-string">'compressed.webp'</span>, img, [cv2.IMWRITE_WEBP_QUALITY, <span class="hljs-number">80</span>])
</code></pre>
<p>And here is our result:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724128407734/491bee37-d938-454b-9b0a-14cb1fd08437.webp" alt class="image--center mx-auto" /></p>
<p>These two techniques are great for compressing large amounts of data. You can write scripts to compress thousands or hundreds of thousands of images automatically.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Image compression is fantastic. It’s essential for computer vision tasks in many ways, especially when saving space or increasing processing speed. There are also many use cases outside of computer vision anytime you want to reduce hard drive space or save bandwidth. Image compression can help a lot.</p>
<p>By understanding the theory behind it and applying it, you can do some powerful things with your projects.</p>
<p>Remember, the key to effective compression is finding the sweet spot between file size reduction and maintaining acceptable visual quality for your application.</p>
<p>Thanks for reading, and feel free to <a target="_blank" href="https://www.linkedin.com/in/jeremycmorgan/">reach out</a> if you have any comments or questions!</p>
<blockquote>
<p>Also if you're into Computer Vision and other cool AI tech, you should subscribe to my <a target="_blank" href="https://mailchi.mp/4961a415a64f/learn-ai-newsletter">AI Architect Newsletter</a> to keep up with the latest stuff!</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Running an LLM Locally on Your Own Machine]]></title><description><![CDATA[So, you want to run a ChatGPT-like chatbot on your own computer? Want to learn more LLMs or just be free to chat away without others seeing what you’re saying? This is an excellent option for doing just that.
I’ve been running several LLMs and other ...]]></description><link>https://www.jeremymorgan.dev/running-an-llm-locally-on-your-own-machine</link><guid isPermaLink="true">https://www.jeremymorgan.dev/running-an-llm-locally-on-your-own-machine</guid><category><![CDATA[generative ai]]></category><category><![CDATA[large language models]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Developer]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[chatbot]]></category><category><![CDATA[Chat-GPT]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[transformers]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Wed, 06 Dec 2023 15:56:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701878038360/93367706-143a-47b3-972b-a90fcf8f6930.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, you want to run a ChatGPT-like chatbot on your own computer? Want to learn more LLMs or just be free to chat away without others seeing what you’re saying? This is an excellent option for doing just that.</p>
<p>I’ve been running several LLMs and other generative AI tools on my computer lately. I’ve discovered <a target="_blank" href="https://github.com/oobabooga/text-generation-webui/tree/main">this web UI from oobabooga</a> for running models, and it’s incredible. You have a ton of options, and it works great.</p>
<p>That’s what we will set up today in this tutorial.</p>
<h2 id="heading-the-easy-way">The easy way</h2>
<p>If you’re in Windows using WSL, you can run a simple batch file, and it might work great. Super easy.</p>
<p>Clone the repo:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/oobabooga/text-generation-webui.git
</code></pre>
<p>Then run the batch file:</p>
<pre><code class="lang-bash">start_wsl.bat
</code></pre>
<p>It will ask you to choose your GPU/platform setup:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-wsl.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>And it’s up and running:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-wsl-01.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>If this works, skip to the <strong>Run the WebUI</strong> step.</p>
<p>But if it fails (which I’ve seen), you must do it manually. Below are the instructions to install it manually in WSL. It’s also the instructions to install this in regular old Linux. Let’s get started.</p>
<h2 id="heading-install-anaconda">Install Anaconda</h2>
<p>I’m using Ubuntu in WSL. So here are the commands we’ll run:</p>
<pre><code class="lang-bash">sudo apt-get update
</code></pre>
<p>Always a good idea.</p>
<pre><code class="lang-bash">sudo apt-get install wget
</code></pre>
<p>Change into the tmp directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> /tmp
</code></pre>
<p>Then, we want to get the latest version of the installation script from this directory. At the time of this writing, this is the most current version for Linux-x86_64:</p>
<pre><code class="lang-bash">wget https://repo.anaconda.com/archive/Anaconda3-2023.09-0-Linux-x86_64.sh
</code></pre>
<p>This script is <em>huge</em>. After it’s done downloading, you should see something like this:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-00.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Then you’ll want to validate it:</p>
<pre><code class="lang-bash">sha256sum Anaconda3-2023.09-0-Linux-x86_64.sh
</code></pre>
<p>and if you don’t see any errors, you’re good to go:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-01.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Now it’s time to run it!</p>
<pre><code class="lang-bash">bash Anaconda3-2023.09-0-Linux-x86_64.sh
</code></pre>
<p>Accept the license terms (if you want to use it) and press enter.</p>
<p>It will ask where you want to install it. I chose the default location:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-02.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Then, grab a beverage and wait a while. I prefer ice water with lemon.</p>
<p>It’s going to ask if you want to initialize Conda automatically. I do a ton of Python stuff, so I select yes. Choose whatever works best for you.</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-03.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Now exit the shell and restart your WSL window.</p>
<h3 id="heading-install-the-text-ui">Install the Text UI</h3>
<p>Next, we will install the Web UI interface for our models. This is a Gradio web UI for Large Language Models.</p>
<p>As stated in the repo, their goal is to become the <a target="_blank" href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">AUTOMATIC1111/stable-diffusion-webui</a> of text generation.</p>
<p>Clone it into a folder you’ll want to work in:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/oobabooga/text-generation-webui.git
</code></pre>
<p>Now type in</p>
<p><code>conda deactivate</code></p>
<p>If you have a base version running. We’ll then create a new environment:</p>
<pre><code class="lang-bash">conda create -n textgen python=3.11
conda activate textgen
</code></pre>
<p>If you see (textgen) in front of your prompt, it’s working.</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-04.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Now, we need to install PyTorch. I’m using an NVidia card, so I type in:</p>
<pre><code class="lang-bash">pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
</code></pre>
<p>If you aren’t using an NVidia card and want to do CPU only, use this:</p>
<pre><code class="lang-bash">pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
</code></pre>
<p>You’ll see a lot of this:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-05.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Wait for it to finish. If you are running an NVidia card, you may need to do this:</p>
<pre><code class="lang-bash">conda install -y -c <span class="hljs-string">"nvidia/label/cuda-12.1.0"</span> cuda-runtime
</code></pre>
<p>Next, we need to install some more dependencies. This will depend on your machine.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> text-generation-webui
pip install -r &lt;requirements file according to table below&gt;
</code></pre>
<p>Requirements file to use:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>GPU</td><td>CPU</td><td>requirements file to use</td></tr>
</thead>
<tbody>
<tr>
<td>NVIDIA</td><td>has AVX2</td><td><code>requirements.txt</code></td></tr>
<tr>
<td>NVIDIA</td><td>no AVX2</td><td><code>requirements_noavx2.txt</code></td></tr>
<tr>
<td>AMD</td><td>has AVX2</td><td><code>requirements_amd.txt</code></td></tr>
<tr>
<td>AMD</td><td>no AVX2</td><td><code>requirements_amd_noavx2.txt</code></td></tr>
<tr>
<td>CPU only</td><td>has AVX2</td><td><code>requirements_cpu_only.txt</code></td></tr>
<tr>
<td>CPU only</td><td>no AVX2</td><td><code>requirements_cpu_only_noavx2.txt</code></td></tr>
</tbody>
</table>
</div><p>(this comes from the instructions)</p>
<p>After everything is installed, you should be ready to run the WebUI.</p>
<h2 id="heading-run-the-webui">Run the WebUI</h2>
<p>Now we’re ready to run! In the <code>text-generation-webui</code> directory, run the following:</p>
<pre><code class="lang-bash">python server.py
</code></pre>
<p>And you should see this:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-06.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Awesome! Let’s load it up in the web browser:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-07.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>If you see this, you’re golden! However, you can’t do anything with it yet. You’ll need a model.</p>
<h2 id="heading-downloading-an-llm-model">Downloading an LLM model</h2>
<p>Your models will be downloaded and placed in the <code>text-generation-webui/models</code> folder. There are several ways to download the models, but the easiest way is in the web UI.</p>
<p>Click on “Model” in the top menu:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-08.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Here, you can click on “Download model or Lora” and put in the URL for a model hosted on <a target="_blank" href="https://huggingface.co/models?pipeline_tag=text-generation&amp;sort=downloads">Hugging Face</a>.</p>
<p>There are tons to choose from. The first one I will load up is the <a target="_blank" href="https://huggingface.co/TheBloke/Nous-Hermes-13B-GPTQ">Hermes 13B GPTQ</a>.</p>
<p>I only need to place the username/model path from Hugging Face to do this.</p>
<pre><code class="lang-plaintext">TheBloke/Nous-Hermes-13B-GPTQ
</code></pre>
<p>And I can then download it through the web interface.</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-09.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>After I click refresh, I can see the new model available:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-10.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Select it, and press load. Now we’re ready to go!</p>
<h2 id="heading-having-a-chat">Having a Chat</h2>
<p>There are a ton of parameters you can adjust. You can get lost in the settings, and once I learn more about it, I’ll certainly share it here.</p>
<p>Here was my test chat:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-11.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Hey! It works! Awesome, and it’s running locally on my machine.</p>
<p>I decided to ask it about a coding problem:</p>
<p><img src="https://www.jeremymorgan.com/images/blog/generative-ai/how-to-run-chat-gpt-like-llm-locally/how-to-run-chat-gpt-like-llm-locally-12.webp" alt="“How to run a ChatGPT like LLM locally”" /></p>
<p>Okay, not quite as good as GitHub Copilot or ChatGPT, but it’s an answer! I’ll play around with this and share what I’ve learned soon.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You may want to run a large language model locally on your own machine for many reasons. I’m doing it because I want to understand LLMs better and understand how to tune and train them. I am deeply curious about the process and love playing with it. You may have your own reasons for doing it, such as content generation or a chatbot to joke around with. The fact that you don’t have to be connected to the internet or pay a monthly fee is awesome.</p>
<p>What are you doing with LLMs today? <a target="_blank" href="https://www.twitter.com/JeremyCMorgan">Let me know! Let’s talk</a>.</p>
<p>Also if you have any questions or comments, feel free to reach out.</p>
<p>Happy hacking!</p>
<p><strong>If you're into Computer Vision and other cool AI tech, you should subscribe to my</strong> <a target="_blank" href="https://mailchi.mp/4961a415a64f/learn-ai-newsletter"><strong>AI Architect Newsletter</strong></a> <strong>to keep up with the latest stuff!</strong></p>
]]></content:encoded></item><item><title><![CDATA[How to Read Text From an Image with Python]]></title><description><![CDATA[If you want to read text from an image with a simple Python script, this tutorial is for you. Thanks to the work of many great people over the last few decades, you can read the text from an image with a few lines of code. Really! Let’s jump in.

Wha...]]></description><link>https://www.jeremymorgan.dev/how-to-read-text-from-an-image-with-python</link><guid isPermaLink="true">https://www.jeremymorgan.dev/how-to-read-text-from-an-image-with-python</guid><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[python beginner]]></category><category><![CDATA[python projects]]></category><category><![CDATA[AI]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[image processing]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Mon, 23 Oct 2023 16:46:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1698079502862/d3e88328-9e11-4ddf-9f2c-4c5470c39259.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you want to read text from an image with a simple Python script, this tutorial is for you. Thanks to the work of many great people over the last few decades, you can read the text from an image with a few lines of code. Really! Let’s jump in.</p>
<p><img src="https://www.jeremymorgan.com/images/tutorials/python-tutorials/read-text-from-image/read-text-from-image-00.webp" alt="How to read text from an image with Python" /></p>
<h2 id="heading-what-is-ocr-tesseract">What is OCR? Tesseract?</h2>
<p>Optical Character Recognition, or OCR has <a target="_blank" href="https://www.docsumo.com/blog/optical-character-recognition-history">been around for a long time</a>. It's a technique that “reads” different types of documents into editable and searchable text. It works by recognizing characters in the image and converting them into machine-readable text. It’s a lot of magic, but it works well.</p>
<p><a target="_blank" href="https://github.com/tesseract-ocr/tesseract">Tesseract</a> is an open-source OCR engine developed by Google. It is highly accurate and supports multiple languages. This library will do all the heavy lifting for us. We’ll use it in this tutorial to quickly read the text in some images.</p>
<h3 id="heading-step-1-set-up-your-python-environment">Step 1: Set up your Python Environment</h3>
<p>First, you’ll need to make sure Python is installed. We’re going to create a virtual environment.</p>
<ul>
<li><p><a target="_blank" href="https://www.pythonhelp.org/learn/introduction/setting-up-development-environment-windows/">How to install Python and set up a virtual environment in Windows</a></p>
</li>
<li><p><a target="_blank" href="https://www.pythonhelp.org/learn/introduction/setting-up-development-environment-mac/">How to set up your Python environment on a Mac</a></p>
</li>
<li><p><a target="_blank" href="https://www.pythonhelp.org/learn/introduction/setting-up-development-environment-linux/">How to setup Python environment in Linux</a></p>
</li>
</ul>
<p>I’m using Linux, so I’ll create a directory named <code>textreader</code> and type in</p>
<pre><code class="lang-plaintext">python -m venv textreader
</code></pre>
<p>Then</p>
<pre><code class="lang-plaintext">source textreader/bin/activate
</code></pre>
<h3 id="heading-step-2-install-the-required-libraries">Step 2: Install the Required Libraries</h3>
<p>First, we’ll need to install Tesseract on your system. Here are the <a target="_blank" href="https://tesseract-ocr.github.io/tessdoc/Installation.html">instructions to install Tesseract</a> on your chosen operating system.</p>
<p>Make sure Tesseract is installed by typing:</p>
<pre><code class="lang-plaintext">tesseract -v
</code></pre>
<p>and you should see output that looks like this:</p>
<p><img src="https://www.jeremymorgan.com/images/tutorials/python-tutorials/read-text-from-image/read-text-from-image-01.webp" alt="How to read text from an image with Python" /></p>
<p>Then, we’ll install a couple of Python libraries.</p>
<p><a target="_blank" href="https://pypi.org/project/pytesseract/">Pytesseract</a> is a Python library that is a wrapper for the Tesseract OCR engine. This makes it easy to use in Python applications. We’ll install that and Pillow.</p>
<p><a target="_blank" href="https://pypi.org/project/Pillow/">Pillow</a> is the Python Image Library. It’s used for image processing and manipulation. It’s used to pre-process images before applying OCR techniques. It does things like image thresholding and other steps to the image to enhance the accuracy of the reading.</p>
<p>Next, we’ll install Pytesseract and Pillow together for our first application:</p>
<pre><code class="lang-plaintext">pip install pytesseract
pip install pillow
</code></pre>
<p>Your output should look something like this:</p>
<p><img src="https://www.jeremymorgan.com/images/tutorials/python-tutorials/read-text-from-image/read-text-from-image-02.webp" alt="How to read text from an image with Python" /></p>
<p>In some cases, like above, it may say the requirement is already satisfied for Pillow.</p>
<p>And we’re ready to go.</p>
<h3 id="heading-step-3-select-your-image">Step 3: Select your Image</h3>
<p>To start, I’m going to choose something easy. I’ll use a screenshot from my website. This will be clear, easy-to-read text that should work great.</p>
<p><img src="https://www.jeremymorgan.com/images/tutorials/python-tutorials/read-text-from-image/read-text-from-image-03.webp" alt="How to read text from an image with Python" /></p>
<p>I’ll save that as image-1.jpg in my folder.</p>
<h3 id="heading-step-4-write-the-script">Step 4: Write the Script</h3>
<p>Now, we’re ready to build our Python script to read the text from that image and output it to the screen.</p>
<p>First, we’ll import the libraries:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pytesseract
<span class="hljs-keyword">from</span> PIL <span class="hljs-keyword">import</span> Image
</code></pre>
<p>Then open the image:</p>
<pre><code class="lang-python">image = Image.open(<span class="hljs-string">'image-1.jpg'</span>)
</code></pre>
<p>And then, we’ll use Tesseract to convert the text in the image to a string. Didn’t I say this library does <em>all</em> the heavy lifting for us?</p>
<pre><code class="lang-python">text = pytesseract.image_to_string(image)
</code></pre>
<p>Finally, we’ll print it out:</p>
<pre><code class="lang-python">print(text)
</code></pre>
<p>Let’s run it and see what it looks like.</p>
<h3 id="heading-step-5-watch-the-magic-happen">Step 5: Watch the Magic Happen</h3>
<p>We run our script and get this:</p>
<p><img src="https://www.jeremymorgan.com/images/tutorials/python-tutorials/read-text-from-image/read-text-from-image-04.webp" alt="How to read text from an image with Python" /></p>
<p>Awesome! So it’s not perfect, but it’s pretty darn good. You can read the text from the image we sent, and it’s somewhat formatted the way it is in the image. That’s awesome!</p>
<p>Congrats! You can now read the text from images in Python. Next, we’ll look at some more advanced stuff.</p>
<h3 id="heading-learning-the-limitations">Learning the Limitations</h3>
<p>In our first example, we had a very clear image. The text is formatted and crisp in that image, so it’s easy to read. Let’s step it up a bit.</p>
<p>I picked a more challenging image, one from <a target="_blank" href="https://www.pexels.com/photo/welcome-to-our-home-print-brown-wooden-wall-decor-163046/">Pexels</a>, that isn’t quite so easy.</p>
<p><img src="https://www.jeremymorgan.com/images/tutorials/python-tutorials/read-text-from-image/read-text-from-image-05.webp" alt="How to read text from an image with Python" /></p>
<p>Let’s see what the output is when reading this image:</p>
<p><img src="https://www.jeremymorgan.com/images/tutorials/python-tutorials/read-text-from-image/read-text-from-image-06.webp" alt="How to read text from an image with Python" /></p>
<p>Oof. Nothing. I included this because it’s important to know the limitations of this process. Unusual fonts and different angles will affect how well this works. There isn’t much we can do to read this image without some extensive work.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In this tutorial, we learned how to use Tesseract to read text from an image and put it into a machine-readable form. We can read many other things with OCR, and we’ll deep dive into some of this stuff in future articles.</p>
<p>Feel free to play around with this and see what you can come up with! In a future tutorial, we’ll use OpenCV to refine things and do more pre-processing of the images we’ll read from. It will be fun.</p>
<p>Bookmark this blog and come back for more cool Python tutorials.</p>
<p>Questions? Comments? <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">Yell at me!</a></p>
<p><strong>If you're into Computer Vision and other cool AI tech, you should subscribe to my</strong> <a target="_blank" href="https://mailchi.mp/4961a415a64f/learn-ai-newsletter"><strong>AI Architect Newsletter</strong></a> <strong>to keep up with the latest stuff!</strong></p>
]]></content:encoded></item><item><title><![CDATA[Dall-E 3 vs Midjourney - Which is better?]]></title><description><![CDATA[OpenAI recently released the third version of DALL-E. Of course, the biggest news from this is the integration with ChatGPT. This could be a huge step forward in prompt creation. But we’re not going to focus on that part today. We will examine the ou...]]></description><link>https://www.jeremymorgan.dev/dall-e-3-vs-midjourney-which-is-better</link><guid isPermaLink="true">https://www.jeremymorgan.dev/dall-e-3-vs-midjourney-which-is-better</guid><category><![CDATA[AI]]></category><category><![CDATA[midjourney]]></category><category><![CDATA[dall-e3]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[generative art]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Midjourney ai]]></category><category><![CDATA[Prompt Engineering]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Thu, 19 Oct 2023 17:14:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697735633314/d3224ce2-f6a8-4637-bf20-2053f6071ad1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>OpenAI recently released the third version of DALL-E. Of course, the biggest news from this is the integration with ChatGPT. This could be a huge step forward in prompt creation. But we’re not going to focus on that part today. We will examine the output of <a target="_blank" href="https://labs.openai.com/">Dall-E 3</a> and see if it’s better than <a target="_blank" href="https://www.midjourney.com/app/">Midjourney</a>.</p>
<p>Recently, I’ve been using Midjourney more for image generation. Dall-E 2 was good, but not as good as Midjourney. Has that changed? Let’s throw a few simple prompts at it to see.</p>
<h2 id="heading-prompt-a-realistic-portrait-of-a-cat-wearing-a-wizards-hat">Prompt: A realistic portrait of a cat wearing a wizard’s hat</h2>
<h4 id="heading-dall-e-3">Dall-E 3</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/76ae88be9e5ee4c6def94347a555285fb71e3680/b86e3/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-00.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<h4 id="heading-midjourney">Midjourney</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/6cd20456b413998d48d72b2acd1690738c2c3303/25f68/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-01.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<p>This first example is clear. Midjourney definitely looks better. More realistic and detailed. Our prompt may be too simplistic. Let’s try something else.</p>
<h4 id="heading-prompt-a-red-tesla-roadster-driving-down-a-highway-illustrated-in-the-style-of-looney-tunes-in-the-background-is-a-typical-looney-tunes-city-however-the-car-is-a-modern-tesla">Prompt: <em>A red Tesla Roadster driving down a highway. Illustrated in the style of Looney Tunes. In the background is a typical Looney Tunes city. However, the car is a modern Tesla.</em></h4>
<h4 id="heading-dall-e-3-1">Dall-E 3</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/a7bda24b609e338e95f45362896450008cb247d9/ad741/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-02.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<h4 id="heading-midjourney-1">Midjourney</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/3929a7a351541dfb8af32bee3c9dab445ebfd01c/c57c6/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-03.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<p>Okay, once again, Midjourney is blowing Dall-E out of the water. Neither look like a Looney Tunes cartoon, but Midjourney certainly comes closer.</p>
<p>Let’s take some prompts from <a target="_blank" href="https://mspoweruser.com/best-dall-e-2-prompts/">“The 30 best Dall-E 2 prompts for amazing results”</a> and see what we can come up with.</p>
<h4 id="heading-prompt-whimsical-painting-of-an-enchanted-forest-with-mythical-creatures-vibrant-colors-and-intricate-details">Prompt: <em>Whimsical painting of an enchanted forest with mythical creatures, vibrant colors, and intricate details.</em></h4>
<h4 id="heading-dall-e-3-2">Dall-E 3</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/48ce317cad777b65a64aefcfa75a892a66d31895/ec74d/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-04.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<h4 id="heading-midjourney-2">Midjourney</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/15e7ff3aaccde83f75ddeb38abd9a882eaa392e1/2de22/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-05.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<h4 id="heading-prompt-victorian-era-painting-of-a-masquerade-ball-with-elaborate-costumes-contrasting-colors-and-soft-lighting">Prompt: <em>Victorian-era painting of a masquerade ball with elaborate costumes, contrasting colors, and soft lighting.</em></h4>
<h4 id="heading-dall-e-3-3">Dall-E 3</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/a3530647431713b781f9d2ba81a6aa4775a2e962/b2776/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-06.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<h4 id="heading-midjourney-3">Midjourney</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/1a61abd92e0de2ec15ba8c324584dbdbf43e5e7d/0eb1e/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-07.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<p>This one is a little closer. I like the Midjourney one better, but only because of the paintings’ additional detail and different framing. The angles and framing of the Dall-E renderings are dull, and the subject isn’t clear. But it’s fairly close.</p>
<p>Let’s try the #1 best prompt according to that article.</p>
<h4 id="heading-prompt-realistic-painting-of-a-dystopian-industrial-city-with-towering-factories-pollution-filled-air-and-a-gloomy-sky">Prompt: <em>Realistic painting of a dystopian industrial city with towering factories, pollution-filled air, and a gloomy sky.</em></h4>
<h4 id="heading-dall-e-3-4">Dall-E 3</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/7967bb9be92c14a7c0814d20586e681f8a80bbe4/00e0d/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-08.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<h4 id="heading-midjourney-4">Midjourney</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/05ecf92fadacae1db58338d54ff9eadd4ab0a5ae/e000a/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-09.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<p>With this one, the style and the rendering of Dall-E 3 aren’t bad. It looks more like an actual painting. However, the Midjourney one is far more exciting and engaging.</p>
<p>Let’s try one of my favorite prompts: putting a 2005 Mustang GT <a target="_blank" href="https://www.instagram.com/p/CgdCa-AICed/">Like mine</a> in different settings.</p>
<h4 id="heading-prompt-a-3d-render-of-a-red-2005-ford-mustang-gt-on-a-deserted-country-highway">Prompt: <em>a 3D render of a red 2005 Ford Mustang GT on a deserted country highway</em></h4>
<h4 id="heading-dall-e-3-5">Dall-E 3</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/1af4627b078d0b71339104178848110493d72853/cf982/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-10.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<h4 id="heading-midjourney-5">Midjourney</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/9c06b3318d12fdd0369b2df82ab74269feed2342/50c91/images/blog/generative-ai/dall-e-3-vs-midjourney/dall-e-3-vs-midjourney-11.webp" alt="“Dall-E 3 vs Midjourney”" /></p>
<p>The difference here is drastic. One certainly looks more like a 3D rendering than the other.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Midjourney still produces better images overall than Dall-E. However, this is my experience, and with enough prompt tuning, I’m confident someone can make a fool of me and generate a much better image with Dall-E. Also, the ChatGPT integration is a nice bonus. Being able to tweak prompts using advanced techniques makes all the difference.</p>
<p>What has your experience been like? <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">Let me know!</a></p>
<p><strong>If you're into Computer Vision and other cool AI tech, you should subscribe to my</strong> <a target="_blank" href="https://mailchi.mp/4961a415a64f/learn-ai-newsletter"><strong>AI Architect Newsletter</strong></a> <strong>to keep up with the latest stuff!</strong></p>
]]></content:encoded></item><item><title><![CDATA[Generating AI Images from your own PC]]></title><description><![CDATA[You want to create AI-generated images. You’ve probably signed up with Dall-E, or Bing, or Midjourney. You’ve created some cool stuff. If you’re reading this, you want to generate those images on your own computer. There is no website or discord to d...]]></description><link>https://www.jeremymorgan.dev/generating-ai-images-from-your-own-pc</link><guid isPermaLink="true">https://www.jeremymorgan.dev/generating-ai-images-from-your-own-pc</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[generative art]]></category><category><![CDATA[Artificial Intelligence]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[stable diffusion]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Mon, 02 Oct 2023 17:49:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696268838147/49abddc6-cfd2-42c7-a13a-374329a393e3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You want to create AI-generated images. You’ve probably signed up with Dall-E, or Bing, or Midjourney. You’ve created some cool stuff. If you’re reading this, you want to generate those images on your <em>own computer</em>. There is no website or discord to deal with. Running locally on your machine.</p>
<p>Why would anyone do this? Here are a few reasons:</p>
<ul>
<li><p>It’s educational</p>
</li>
<li><p>You can generate as many images as you want</p>
</li>
<li><p>It’s free</p>
</li>
</ul>
<p>With this tutorial’s help, you can generate images with AI on your own computer with <a target="_blank" href="https://github.com/Stability-AI/stablediffusion">Stable Diffusion</a>.</p>
<p>Let’s get started.</p>
<h2 id="heading-step-1-requirements">Step 1: Requirements</h2>
<p>There are some requirements for doing this on your own machine. It must have:</p>
<h3 id="heading-hardware">Hardware:</h3>
<ul>
<li><p>A modern AMD or Intel CPU</p>
</li>
<li><p>16 Gigabytes of RAM</p>
</li>
<li><p>SATA or NVMe solid state drive</p>
</li>
<li><p>NVidia GeForce GPU with at least 8 gigabytes of GDDR6 RAM</p>
</li>
</ul>
<h3 id="heading-software">Software:</h3>
<ul>
<li><p>You must have Python installed (<a target="_blank" href="https://www.pythonhelp.org/learn/introduction/setting-up-development-environment-windows/">How to install Python in Windows 11</a>).</p>
</li>
<li><p>You can use Anaconda if you want (<a target="_blank" href="https://www.jeremymorgan.com/tutorials/python-tutorials/how-to-install-anaconda-windows/">How to install Anaconda in Windows 11</a>).</p>
</li>
<li><p>You must have Git Installed (<a target="_blank" href="https://www.simplilearn.com/tutorials/git-tutorial/git-installation-on-windows">How to install Git in Windows 11</a>).</p>
</li>
<li><p>You need an account at <a target="_blank" href="https://huggingface.co/">HuggingFace</a> (it’s a free account)</p>
</li>
</ul>
<p>Check your Python version to make sure it’s up to snuff:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/2165b556678b83c8a3ef8f13bff8bbffd125f8f7/df3d3/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-00.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>And make sure Git is installed:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/c2f1dcc2497f0f420406760117484bf43d8edfa0/d99eb/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-01.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>And you’re ready to go.</p>
<h2 id="heading-step-2-install-stable-diffusion-web-ui">Step 2: Install Stable Diffusion Web UI</h2>
<p>Create a folder somewhere where you want the software to live. Go to that folder:</p>
<p><code>cd /your/project/directory</code></p>
<p>(for me it’s C:\Users\jerem\Projects)</p>
<p>Then clone the repository:</p>
<p><code>git clone</code> <a target="_blank" href="https://github.com/AUTOMATIC1111/stable-diffusion-webui.git"><code>https://github.com/AUTOMATIC1111/stable-diffusion-webui.git</code></a> <code>stable-diffusion-demo</code></p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/f1034dc1c6ce5bc42f6ac2b7f265f2d50a474cc0/b57af/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-02.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>You should see the folder you just created:</p>
<p><code>stable-diffusion-demo</code></p>
<p>Now we need to go into the models folder:</p>
<p><code>cd stable-diffusion-demo\models\Stable-diffusion</code></p>
<p>It should look like this:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/a76eccef4acadd6c6db176bd1b7ca92bee17ddc9/20598/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-03.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>This is where we’ll put the Stable Diffusion models when we download them.</p>
<h2 id="heading-step-3-download-the-model-from-hugging-face">Step 3: Download the Model from Hugging Face</h2>
<p>Next, we need a stable diffusion model, and we can get one from <a target="_blank" href="https://huggingface.co/">Hugging Face</a>.</p>
<p>Let’s try the <a target="_blank" href="https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt">v1-5-pruned-emaonly.ckpt</a> model. Download it to the <code>stable-diffusion-demo/models/Stable-diffusion</code> folder:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/837589a9170d2be610ac837120bb7e7f5d2363d9/45ff3/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-04.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>It might take a while to download (4GB at the time of this article).</p>
<p>Ok, now.. you’re ready to run the web app!</p>
<h3 id="heading-step-4-run-the-stable-diffusion-web-ui">Step 4: Run the Stable Diffusion Web UI</h3>
<p>At your command prompt, make sure you’re in the folder you created in step 2.</p>
<p><code>cd stable-diffusion-demo</code></p>
<p>Then run <code>webui-user.bat</code></p>
<p>The first time you do this, it will install everything needed, so it may take a few minutes.</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/2920c9c72da639463ba53cd3b6b18a4333acd423/62f55/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-05.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>Once it’s completed, your command prompt will look something like this:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/fb54d7872bdb841a4b3f369c9cb8e82b61b89e3c/ef950/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-06.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>And the web server will be up and running.</p>
<h3 id="heading-step-5-the-web-interface">Step 5: The Web Interface</h3>
<p>Load up the following URL in your web browser:</p>
<p><a target="_blank" href="http://127.0.0.1:7860/"><code>http://127.0.0.1:7860/</code></a></p>
<p>It may load up automatically in the browser as well. But you’ll see a full web interface you can use to generate images:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/9aa3e3236917a548f6f710412ca8d23961b997cc/d6d42/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-07.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>Awesome! Let’s try it out!</p>
<p>The sampling method is the first thing you might notice in the generation tab. There are a LOT of them:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/edbdf0a6363469508b2969e8df57de82f2829e08/89cf9/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-08.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>I suggest spending some time adjusting and playing with this. But for now, we’ll use <code>DPM++ 2M Karras</code> that’s selected as default.</p>
<p>Here are some settings to pay attention to:</p>
<ul>
<li><p><strong>Sampling steps</strong> - This will repeatedly try to improve the image as it generates. Low values will be low quality, but fast. High values will be higher quality and slower to generate.</p>
</li>
<li><p><strong>Width and height</strong> - These will control the size of the generated image. Like above, larger images will look better and take longer.</p>
</li>
<li><p><strong>CFG Scale</strong> - This will change how “random” the result is. A higher value will attempt to take the prompt as literally as possible. Lower values will produce more creative results.</p>
</li>
<li><p><strong>Seed</strong> - This value determines the output of the random number generator. You can use it as a key. If you use the same prompt with the same seed, you’ll get the same image. You can create many variations from the same “key”.</p>
</li>
</ul>
<p>So today, I will leave everything at the default settings. The only thing I will do is create a <code>seed</code> to progressively improve the same image as we go.</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/4d9c9c4d1203bd6dcc828cd8290941a484891228/b494d/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-09.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>And I’ll add in a prompt to make something silly.</p>
<p>‘a photo of an anthropomorphic frog riding a skateboard in Times Square`</p>
<p>It took about a minute to generate this image. (Laptop with RTX 3060)</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/211861faf97f96a3ca908bc9ba70d4459d53cb94/303ca/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-10.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>And it’s pretty cool! It’s impressive, considering it’s being run on my local machine. Let’s improve this image!</p>
<h3 id="heading-step-6-improving-images">Step 6: Improving Images</h3>
<p>Here’s the actual image I produced:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/cf45eea77cb7ade627ac65068687276f72748f18/cde76/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-11.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>Let’s mess with the settings to see what we can improve. Let’s change the Sampling steps. This is how often it goes through the image and tries to improve it. The default setting is <code>20</code>. Let’s set it to the maximum number of <code>150</code>. This will take more than a minute to produce now.</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/485d519cdcafe319d851874e2800002e16eb7a6b/2e310/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-12.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>I will leave the prompt and the seed the same and click “Generate” again.</p>
<p>I’ll warn you: it can work the GPU when you do this.</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/556e5962c8ff7d50e0476946a150ec7aa3b67cd7/0ca64/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-13.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>Here is the result after <strong>150</strong> sampling steps:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/7054d90a1f483164bb9dda770f70c0aa31a8b907/77895/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-14.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>So it’s certainly different, but not in a significant way. We don’t want to scale up our generating time by 10x for this small change. I’ll change it back to <code>20</code> and change <code>CFG Scale</code> to <code>5</code>. This will make the model more “creative”.</p>
<p>Here are the settings:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/ddd5ab9ab7493d74782ff6d2931d8bc770a30a0a/99819/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-15.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>And here is the result:</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/d77e6a8879cb925ae0c6cd12152a6e11e6cefdf1/ce11f/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-16.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>Ok, that’s too creative. But you can see the changes that occur when you adjust it. You can also use different sampling methods as well:</p>
<h4 id="heading-dpm-sde-karras">DPM++ SDE Karras</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/d3ab144195a526361b2e571d85b8060ce6aa1eb2/c24f2/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-17.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>This model’s output is kind of neat because it shows the frog skateboarding better.</p>
<h4 id="heading-euler">Euler</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/ee55b16362b7d63dd0e3608a2d1d29031be1337a/1cd62/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-18.webp" alt="“How to Install Free AI image Generator”" /></p>
<h4 id="heading-dpm-3m-sde">DPM++ 3M SDE</h4>
<p><img src="https://d33wubrfki0l68.cloudfront.net/4cd78ca3bf1784f2a9eab03476e80737918caf10/8a72b/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-19.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>When attempting to draw things in cartoon, especially anthropomorphic models, it can come out very bizarre.</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/5ac40456a9a656c01cfd3afcf6c9c4575a183332/758fe/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-20.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>But that’s all there is to it!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, we:</p>
<ul>
<li><p>Installed the Stable Diffusion Web UI</p>
</li>
<li><p>Downloaded a model</p>
</li>
<li><p>Generated AI images</p>
</li>
<li><p>Modified the output</p>
</li>
</ul>
<p>All from our local machine! Here are the benefits and downsides of this method compared to something like Midjourney:</p>
<p>Pros:</p>
<ul>
<li><p>It’s free!</p>
</li>
<li><p>Generate as many images as you like</p>
</li>
<li><p>Runs locally and is not dependent on an internet connection</p>
</li>
</ul>
<p>Cons:</p>
<ul>
<li><p>Nowhere near the level of quality as Midjourney and others</p>
</li>
<li><p>You need a good machine to run this</p>
</li>
<li><p>It may take longer to generate images</p>
</li>
</ul>
<p><img src="https://d33wubrfki0l68.cloudfront.net/c9881832c004cf75e5e5469a553a356d8d7a56f4/950e0/images/tutorials/generative-ai/how-to-create-ai-images/how-to-create-ai-images-21.webp" alt="“How to Install Free AI image Generator”" /></p>
<p>So there you have it! Now, you can generate images on your machine and show your creations to the world. We are barely getting started with the possibilities available from this model. We’ll explore it more in the coming weeks.</p>
<p>If you have any questions, feel free to reach out!</p>
<p>– Jeremy</p>
<p>Questions? Comments? <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">Yell at me!</a></p>
<p><strong>If you're into Computer Vision and other cool AI tech, you should subscribe to my</strong> <a target="_blank" href="https://mailchi.mp/4961a415a64f/learn-ai-newsletter"><strong>AI Architect Newsletter</strong></a> <strong>to keep up with the latest stuff!</strong></p>
]]></content:encoded></item><item><title><![CDATA[The Ultimate Guide to Calling a Function in Python]]></title><description><![CDATA[Python is one of my favorite programming languages. It’s simple and versatile. There’s a reason it’s so popular. It’s fun to use, and you can get a lot of work done without writing a ton of code.

“I’ll just write a quick Python script for this” - me...]]></description><link>https://www.jeremymorgan.dev/the-ultimate-guide-to-calling-a-function-in-python</link><guid isPermaLink="true">https://www.jeremymorgan.dev/the-ultimate-guide-to-calling-a-function-in-python</guid><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[python beginner]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[python projects]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Fri, 08 Sep 2023 17:54:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1694195460188/7aee4090-9525-4b0f-b6d1-2e0d504b8751.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Python is one of my favorite programming languages. It’s simple and versatile. There’s a reason it’s so popular. It’s fun to use, and you can get a lot of work done without writing a ton of code.</p>
<blockquote>
<p><em>“I’ll just write a quick Python script for this”</em> - me, a bunch of times</p>
</blockquote>
<p>One of the things Python does well is abstracting complexity. One example of that is the Python function. A function is a reusable set of instructions to perform a task. Functions aren’t unique to Python; they work mostly the same as in other languages.</p>
<p>If you want to know how to write and call a function in Python, here’s a step-by-step guide.</p>
<h2 id="heading-the-anatomy-of-a-python-function">The Anatomy of a Python Function</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1694195242311/ca051a81-b5cf-4a85-b171-807e564ac330.webp" alt class="image--center mx-auto" /></p>
<p>Before you can call a function, you have to write a function. Thankfully, that’s easy. Let’s look at the main components of a function.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">function_name</span>(<span class="hljs-params">parameters</span>):</span>
    <span class="hljs-comment"># Your code goes here</span>
    <span class="hljs-keyword">return</span>
</code></pre>
<p>The keyword ‘def’ refers to a <em>function definition</em> followed by your chosen function name and parentheses enclosing optional parameters. Parameters are anything you want to put into the function.</p>
<p>The action happens in the <em>function body</em> (Where it says your code goes here). This is where we put statements that make the function do work.</p>
<p>The <em>return</em> keyword allows you to send data <em>out</em> of the function. Note: You don’t always need to return a value. It’s optional.</p>
<p>So the main parts are</p>
<ul>
<li><p>definition (def keyword)</p>
</li>
<li><p>function name</p>
</li>
<li><p>parameters</p>
</li>
<li><p>body (what the function does)</p>
</li>
<li><p>return value</p>
</li>
</ul>
<p>All of these, put together, form a function.</p>
<h2 id="heading-calling-a-python-function">Calling a Python Function</h2>
<p>Once we’ve defined a function, calling it is as straightforward as writing the function’s name followed by parentheses. If our function requires <em>parameters</em>, we place these inside the parentheses. These are optional.</p>
<p>Let’s build a simple function to display Hello World whenever we call it.</p>
<pre><code class="lang-plaintext">def hello_world():
    print("Hello, World!")
</code></pre>
<p>Even though there are no parameters or a return value, this is a function in Python. The “output” of the function is printing Hello World.</p>
<p>We can call this function with this line of code:</p>
<pre><code class="lang-python">hello_world()
</code></pre>
<p><img src="https://d33wubrfki0l68.cloudfront.net/0803006d0cb4d62d204d3f3afb86e4a8013ea47c/dded8/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-01.webp" alt="“How to call a function in Python”" /></p>
<p>And this will output “Hello, World!” to your console whenever we call it.</p>
<p>In fact, if we call it five times:</p>
<pre><code class="lang-python">hello_world()
hello_world()
hello_world()
hello_world()
hello_world()
</code></pre>
<p>It will display the same thing five times.</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/54e5811b77528e3ba6fd097ea509373067553c8a/7bb8f/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-02.webp" alt="“How to call a function in Python”" /></p>
<p>Congratulations, you’ve just written your first function in Python.</p>
<h2 id="heading-the-power-of-function-parameters-in-python">The Power of Function Parameters in Python</h2>
<p><img src="https://d33wubrfki0l68.cloudfront.net/74b83a2fca5dcf29cdb67d6738e1ace6de4a512e/e3ae8/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-03.webp" alt="“How to call a function in Python”" /></p>
<p>Parameters are pieces of data we <em>pass into</em> the function. The work of the function depends on what we pass into it. Parameters enable us to make our Python functions dynamic and reusable. We can define a function that takes parameters, allowing us to pass different arguments each time we call the function.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">greet</span>(<span class="hljs-params">name</span>):</span>
    print(<span class="hljs-string">"Hello, "</span> + name + <span class="hljs-string">"!"</span>)

greet(<span class="hljs-string">"Jeremy"</span>)
greet(<span class="hljs-string">"Susie"</span>)
greet(<span class="hljs-string">"Jim"</span>)
</code></pre>
<p>Here, the <code>greet()</code> function takes one parameter, ‘name’. When we call the function, we can pass any name we wish to greet.</p>
<h2 id="heading-dealing-with-multiple-parameters">Dealing with Multiple Parameters</h2>
<p>Python functions can accept multiple parameters, allowing us to add dynamic inputs to our function calls.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">greet</span>(<span class="hljs-params">name, city</span>):</span>
    print(<span class="hljs-string">"Hello, "</span> + name + <span class="hljs-string">" from "</span> + city + <span class="hljs-string">"!"</span>)

greet(<span class="hljs-string">"Jeremy"</span>, <span class="hljs-string">"Gaston"</span>)
greet(<span class="hljs-string">"Susie"</span>, <span class="hljs-string">"Forest Grove"</span>)
greet(<span class="hljs-string">"Jim"</span>, <span class="hljs-string">"Cornelius"</span>)
</code></pre>
<p>Now <code>greet()</code> takes two parameters, ‘name’ and ‘city’.</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/fd67e56ac2f36f2557edf279e55478efc7ccdd15/fc906/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-04.webp" alt="“How to call a function in Python”" /></p>
<p>And it’s that easy. Congrats, you can now pass data into a function in Python.</p>
<h2 id="heading-leveraging-default-parameters-in-python-functions">Leveraging Default Parameters in Python Functions</h2>
<p><img src="https://d33wubrfki0l68.cloudfront.net/822e7a17bee5f915195c278aaf5ef24293432f35/71f63/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-05.webp" alt="“How to call a function in Python”" /></p>
<p>Sometimes, you want to set a value for a parameter as a default. This allows the function to be called even if no data was passed. Rather than throwing an error, it will populate the value with anything you specify.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">greet</span>(<span class="hljs-params">name=<span class="hljs-string">"User"</span></span>):</span>
    print(<span class="hljs-string">"Hello, "</span> + name + <span class="hljs-string">"!"</span>)

greet()
greet(<span class="hljs-string">"Alice"</span>)
</code></pre>
<p>Here, ‘greet()’ function has a default parameter ‘name’ set to ‘User’. If no argument is passed, the function greets ‘User’. If we provide a name, it greets that name instead.</p>
<p><img src="https://d33wubrfki0l68.cloudfront.net/bcf771e3caee5ac3f39a5be6ffaa588e37d6f586/b4ad2/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-06.webp" alt="“How to call a function in Python”" /></p>
<p>It’s a good way to set defaults.</p>
<h2 id="heading-python-return-statement">Python Return Statement</h2>
<p>The ‘return’ statement exits a function and returns a value. With it, we can store the output of a function in a variable for future use.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">square</span>(<span class="hljs-params">number</span>):</span>
    <span class="hljs-keyword">return</span> number * number

result = square(<span class="hljs-number">5</span>)
print(result)
</code></pre>
<p><img src="https://d33wubrfki0l68.cloudfront.net/c6d29beed2070a434ac6515c2d72e85990bb20c4/76012/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-07.webp" alt="“How to call a function in Python”" /></p>
<p>In this case, <code>square()</code> function returns the square of the input number. The output can be stored in a variable and used later.</p>
<p>The return keyword is used to push values out of the function to use in your program.</p>
<h2 id="heading-functions-are-awesome">Functions are awesome!</h2>
<p><img src="https://d33wubrfki0l68.cloudfront.net/13258187931f220292da323e187ba451c6dacafc/5cc5b/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-08.webp" alt="“How to call a function in Python”" /></p>
<p>Python functions are a powerful tool in a programmer’s arsenal. They encapsulate code blocks for specific tasks, enhancing the readability and maintainability of the code. Understanding how to call a function in Python, employ parameters, and leverage the ‘return’ statement is fundamental to proficient Python programming.</p>
<p>Remember, practice is key! The more you use this, the more second nature it will be for you.</p>
<h2 id="heading-the-versatility-of-variable-arguments-in-python">The Versatility of Variable Arguments in Python</h2>
<p>Python introduces an interesting feature in functions: variable-length arguments. Sometimes we don’t know how many arguments will be passed to a function. Python’s variable arguments (*args and **kwargs) solve this problem.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_numbers</span>(<span class="hljs-params">*args</span>):</span>
    <span class="hljs-keyword">return</span> sum(args)

print(add_numbers(<span class="hljs-number">3</span>, <span class="hljs-number">5</span>, <span class="hljs-number">7</span>, <span class="hljs-number">9</span>))
</code></pre>
<p><img src="https://d33wubrfki0l68.cloudfront.net/4070e4dd5542e6a021e5412a91bfb25315f12d88/8c5b1/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-09.webp" alt="“How to call a function in Python”" /></p>
<p>In the example, <code>add_numbers()</code> uses <code>*args</code> to accept any number of parameters. The function then sums up all the numbers and returns the total.</p>
<h2 id="heading-in-depth-with-python-keyword-arguments">In-Depth with Python Keyword Arguments</h2>
<p>Python functions also allow keyword arguments, which enable us to identify arguments by name. This can be extremely handy when a function has many parameters, and it improves the readability of our code.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">describe_pet</span>(<span class="hljs-params">pet_name, animal_type=<span class="hljs-string">'dog'</span></span>):</span>
    print(<span class="hljs-string">"I have a "</span> + animal_type + <span class="hljs-string">"."</span>)
    print(<span class="hljs-string">"My "</span> + animal_type + <span class="hljs-string">"'s name is "</span> + pet_name + <span class="hljs-string">"."</span>)

describe_pet(pet_name=<span class="hljs-string">'Willie'</span>)
</code></pre>
<p><img src="https://d33wubrfki0l68.cloudfront.net/39d47f165bc30c2352df3875055e6801ba003644/158e2/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-10.webp" alt="“How to call a function in Python”" /></p>
<p>In this scenario, ‘describe_pet()’ is called with the ‘pet_name’ keyword argument. The function also includes a default argument, ‘animal_type’, set to ‘dog’.</p>
<h2 id="heading-grasping-anonymous-functions-in-python-lambda">Grasping Anonymous Functions in Python: Lambda</h2>
<p><img src="https://d33wubrfki0l68.cloudfront.net/554072dea3c67d77f48b95f92692f33810d7e3f5/ac0d8/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-11.webp" alt="“How to call a function in Python”" /></p>
<p>Python’s ‘lambda’ function is a small, anonymous function defined with the ‘lambda’ keyword, rather than ‘def’. Lambda functions can accept any number of arguments but only have one expression.</p>
<pre><code class="lang-python">multiply = <span class="hljs-keyword">lambda</span> x, y: x * y

print(multiply(<span class="hljs-number">5</span>, <span class="hljs-number">4</span>))
</code></pre>
<p><img src="https://d33wubrfki0l68.cloudfront.net/d5988619ac00c8718d13bc42e03af80eef41dfc3/a2546/images/tutorials/python-tutorials/how-to-call-function-python/how-to-call-function-python-12.webp" alt="“How to call a function in Python”" /></p>
<p>In the code above, a lambda function is defined to multiply two numbers, and it’s called with two arguments: 5 and 4. The function returns the result of the multiplication.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congratulations, you now know how to work with functions in Python. We have covered:</p>
<ul>
<li><p>The main parts of a function</p>
</li>
<li><p>Calling a Python function</p>
</li>
<li><p>Using function parameters</p>
</li>
<li><p>Using multiple parameters</p>
</li>
<li><p>Creating default parameters</p>
</li>
<li><p>Returning data with the return statement</p>
</li>
<li><p>Using variable parameters</p>
</li>
<li><p>Leveraging lambda functions</p>
</li>
</ul>
<p>Functions in Python provide a way of organizing and reusing code to create cleaner programming solutions.</p>
<p>As a next step, we recommend you delve deeper into advanced Python function topics such as recursive functions, decorators, and generator functions. I’ll be adding more content like this to this blog very soon.</p>
<p><a target="_blank" href="https://www.jeremymorgan.dev">Bookmark this blog</a> and come back for more cool Python tutorials.</p>
<p>Questions? Comments? <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">Yell at me!</a></p>
]]></content:encoded></item><item><title><![CDATA[A Guide to Blurring Images with OpenCV and Python]]></title><description><![CDATA[If you're reading this, you're probably curious about computer vision or just starting out. You've come to the right place. In this tutorial, we'll learn to blur images using OpenCV and Python. This is one of many OpenCV tutorials I have here.
What i...]]></description><link>https://www.jeremymorgan.dev/how-to-blur-images-with-opencv-and-python</link><guid isPermaLink="true">https://www.jeremymorgan.dev/how-to-blur-images-with-opencv-and-python</guid><category><![CDATA[Python]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[python beginner]]></category><category><![CDATA[python projects]]></category><category><![CDATA[Computer Vision]]></category><category><![CDATA[opencv-python]]></category><category><![CDATA[opencv]]></category><dc:creator><![CDATA[Jeremy Morgan]]></dc:creator><pubDate>Mon, 10 Apr 2023 16:30:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1681144130718/fcb658af-173b-4a74-b020-c679e188e497.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you're reading this, you're probably curious about computer vision or just starting out. You've come to the right place. In this tutorial, we'll learn to blur images using <a target="_blank" href="https://opencv.org">OpenCV</a> and Python. This is one of many <a target="_blank" href="/tags/opencv/">OpenCV tutorials</a> I have here.</p>
<h2 id="heading-what-is-blurring">What is Blurring?</h2>
<p>First off, let's understand what blurring is. Blurring is a process where we reduce the sharpness of an image by blending the colors of neighboring pixels. Basically, we smooth out the image, which can be useful for reducing noise, improving the overall look, or creating artistic effects.</p>
<h2 id="heading-how-does-blurring-work">How Does Blurring Work?</h2>
<p>To blur an image, we need to apply a filter, which is simply a small matrix of numbers called a kernel. Don't worry if you don't know what a matrix is. Think of it as a grid of numbers. The kernel moves across the image, and for each pixel, it calculates the weighted average of the neighboring pixels' colors using the kernel values.</p>
<p>There are different types of kernels, and today we'll explore two common ones: the Gaussian blur and the Median blur.</p>
<h2 id="heading-lets-write-some-code">Let's Write Some Code!</h2>
<p>Before we start coding, make sure you have Python installed, and OpenCV set up. I prefer to use Python virtual environments for my projects, and it's easy to setup.</p>
<pre><code class="lang-bash">python -m venv opencvdemo
</code></pre>
<p>Here I am naming my environment <code>opencvdemo</code> but you can name it whatever you like.</p>
<p>Then, activate OpenCV:</p>
<p>In Linux or Mac:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">source</span> opencvdemo/bin/activate
</code></pre>
<p>In Windows:</p>
<pre><code class="lang-powershell">.\opencvdemo\Scripts\activate
</code></pre>
<p>You'll need to install OpenCV. You can do that by using pip:</p>
<pre><code class="lang-bash">pip install opencv-python
</code></pre>
<p>Now, let's create a Python file and import the necessary libraries.</p>
<p>I named my script <a target="_blank" href="http://blurring.py"><code>blurring.py</code></a>.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> cv2
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
</code></pre>
<h2 id="heading-loading-the-image">Loading the Image</h2>
<p>We'll begin by loading an image from your computer. Replace 'sampleimage.png' with the actual path to your image. (It can also be .jpg if you like).</p>
<pre><code class="lang-python">image = cv2.imread(<span class="hljs-string">'sampleimage.png'</span>)
</code></pre>
<p>Here's the sample I'm using. Feel free to use it yourself. <a target="_blank" href="https://www.jeremymorgan.com/images/tutorials/opencv/how-to-blur-image/how-to-blur-image-00.png">Download it here</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681143885711/fe68406f-7a06-4ea0-8614-77fe35a54f7d.png" alt class="image--center mx-auto" /></p>
<p><em>I generated it with Dall-E, so I don't own it.</em></p>
<h2 id="heading-gaussian-blur">Gaussian Blur</h2>
<p>Let's start with the Gaussian blur. This kernel uses the <strong>Gaussian</strong> function, a bell-shaped curve that spreads the blur evenly around the pixel, making it look more natural.</p>
<p>To apply the Gaussian blur, we'll use OpenCV's <code>GaussianBlur()</code> function, which takes three arguments:</p>
<ul>
<li><p>The input image.</p>
</li>
<li><p>The kernel size (width and height). They must be odd numbers.</p>
</li>
<li><p>The standard deviation (0 means it will be calculated automatically).</p>
</li>
</ul>
<pre><code class="lang-python">gaussian_blur_image = cv2.GaussianBlur(image, (<span class="hljs-number">11</span>, <span class="hljs-number">11</span>), <span class="hljs-number">0</span>)
</code></pre>
<p>Now, let's display the original image and the blurred one side by side:</p>
<pre><code class="lang-python">cv2.imshow(<span class="hljs-string">'Original Image'</span>, image)
cv2.imshow(<span class="hljs-string">'Gaussian Blurred Image'</span>, gaussian_blur_image)
cv2.waitKey(<span class="hljs-number">0</span>)
cv2.destroyAllWindows()
</code></pre>
<p>And it looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681143941481/c0117950-e0f4-40e0-ac06-cdef14afcb20.webp" alt /></p>
<p>Awesome, right? Just a few lines of code, and you can blur images.</p>
<h2 id="heading-median-blur">Median Blur</h2>
<p>Now let's do a Median blur. This kernel takes the median value (the middle value in a sorted list) of the neighboring pixels' colors, which reduces noise while preserving edges.</p>
<p>We'll use OpenCV's <code>medianBlur()</code> function to apply the Median blur, which takes two arguments: the input image and the kernel size (an odd number).</p>
<pre><code class="lang-plaintext">median_blur_image = cv2.medianBlur(image, 11)
</code></pre>
<p>And once again, let's display the original and blurred images:</p>
<pre><code class="lang-plaintext">cv2.imshow('Original Image', image)
cv2.imshow('Median Blurred Image', median_blur_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
</code></pre>
<p>Let's run it!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681143961729/b556a2cf-f20f-4c10-ad59-4edb5ab5d8a6.webp" alt /></p>
<p>This is a different kind of blur you may want to use.</p>
<p>It's pretty easy stuff.</p>
<h2 id="heading-saving-your-images">Saving Your Images</h2>
<p>We know how to display these images on the screen, but that's only part of it. What if you want to save them? It's easy with this one line of code:</p>
<pre><code class="lang-python">cv2.imwrite(<span class="hljs-string">"output.png"</span>, median_blur_image)
</code></pre>
<p>Of course, you can change <code>output.png</code> to whatever you want.</p>
<p>We used the cv2.imread() function to read the image, then performed some modifications to it, and use cv.imwrite() to write it to a new image. Chances are, you'll want to use this later.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, we learned about</p>
<ul>
<li><p>Blurring - what it is and how it works</p>
</li>
<li><p>Loading images with OpenCV</p>
</li>
<li><p>Gaussian blurs</p>
</li>
<li><p>Median blurs</p>
</li>
<li><p>Saving image after modification</p>
</li>
</ul>
<p>Go ahead and experiment with this code. Change the kernel sizes and standard deviations to see how they affect blurring.</p>
<p>Remember, practice makes perfect.</p>
<p>You've unlocked a powerful skill in computer vision, and this is just the beginning! Often you'll want to apply a blur or other modifications before performing operations on an image, so now you know how.</p>
<p>There's much more to explore, like edge detection, object recognition, and even facial recognition. I'll be covering all these in future tutorials. The possibilities are endless.</p>
<p>Questions? Comments? <a target="_blank" href="https://x.com/intent/follow?screen_name=JeremyCMorgan">Yell at me!</a></p>
<p><strong>If you're into Computer Vision and other cool AI tech, you should subscribe to my</strong> <a target="_blank" href="https://mailchi.mp/4961a415a64f/learn-ai-newsletter"><strong>AI Architect Newsletter</strong></a> <strong>to keep up with the latest stuff!</strong></p>
]]></content:encoded></item></channel></rss>