Bevor wir eintauchen:

  • Ich bin Entwickler bei Google Cloud. Gedanken und Meinungen, die hier zum Ausdruck gebracht werden sind ganz meine eigenen.
  • Der vollständige Quellcode für diesen Artikel, einschließlich zukünftiger Updates, ist in verfügbar Dieses Notizbuch Unter der Apache 2.0 -Lizenz.
  • Alle neuen Bilder in diesem Artikel wurden mit Gemini-Nano-Banane unter Verwendung der hier untersuchten Proof-of-Idea-Technology-Pipeline generiert.
  • Sie können mit Gemini kostenlos experimentieren in Google AI Studio. Bitte beachten Sie, dass der programmatische API -Zugang zu Nano Banane ist ein Pay-as-you-go-Service.

🔥 Herausforderung

Wir alle haben vorhandene Bilder, die es wert sind, in verschiedenen Kontexten wiederzuverwenden. Dies würde im Allgemeinen implizieren, dass die Bilder, eine komplexe (wenn nicht unmögliche) Aufgabe, die sehr spezifische Fähigkeiten und Werkzeuge erfordert. Dies erklärt, warum unsere Archive voller vergessener oder ungenutzter Schätze sind. Hochmoderne Sichtmodelle haben sich so sehr weiterentwickelt, dass wir dieses Drawback überdenken können.

Können wir unseren visuellen Archiven neues Leben einhauchen?

Versuchen wir, diese Herausforderung mit den folgenden Schritten zu vervollständigen:

  • 1️⃣ Starten Sie von einem Archivbild, das wir wiederverwenden möchten
  • 2️⃣ extrahieren Sie ein Zeichen, um ein brandneues Referenzbild zu erstellen
  • 3️⃣ generieren Sie eine Reihe von Bildern, um die Reise des Charakters zu veranschaulichen, indem Sie nur Eingabeaufforderungen und die neuen Property verwenden

Dafür werden wir die Fähigkeiten von „Gemini 2.5 Flash Picture“, auch als „Nano Banana“ 🍌 bekannt, untersuchen.


🏁 Setup

🐍 Python -Pakete

Wir werden die folgenden Pakete verwenden:

  • google-genai: Der Google Gen AI Python SDK Nennen wir Gemini mit ein paar Codezeilen
  • networkx Für die Grafikverwaltung

Wir werden auch die folgenden Abhängigkeiten verwenden:

  • pillow Und matplotlib zur Datenvisualisierung
  • tenacity Für Anfrageverwaltung
%pip set up --quiet "google-genai>=1.38.0" "networkx(default)"

🤖 Gen ai sdk

Erstellen a google.genai Kunde:

from google import genai

check_environment()

consumer = genai.Consumer()

Überprüfen Sie Ihre Konfiguration:

check_configuration(consumer)
Utilizing the Vertex AI API with challenge "…" in location "international"

🧠 Gemini -Modell

Für diese Herausforderung wählen wir das neueste Gemini 2.5 -Flash -Bildmodell (derzeit in der Vorschau):

GEMINI_2_5_FLASH_IMAGE = "gemini-2.5-flash-image-preview"

💡 „Gemini 2.5 Flash Picture“ ist auch als „Nano Banana“ bekannt 🍌 🍌


🛠️ Helfer

Definieren Sie einige Helferfunktionen, um Bilder zu generieren und anzuzeigen: 🔽
import IPython.show
import tenacity
from google.genai.errors import ClientError
from google.genai.sorts import GenerateContentConfig, PIL_Image

GEMINI_2_5_FLASH_IMAGE = "gemini-2.5-flash-image-preview"
GENERATION_CONFIG = GenerateContentConfig(response_modalities=("TEXT", "IMAGE"))


def generate_content(sources: checklist(PIL_Image), immediate: str) -> PIL_Image | None:
    immediate = immediate.strip()
    contents = (*sources, immediate) if sources else immediate

    response = None
    for try in get_retrier():
        with try:
            response = consumer.fashions.generate_content(
                mannequin=GEMINI_2_5_FLASH_IMAGE,
                contents=contents,
                config=GENERATION_CONFIG,
            )

    if not response or not response.candidates:
        return None
    if not (content material := response.candidates(0).content material):
        return None
    if not (elements := content material.elements):
        return None

    picture: PIL_Image | None = None
    for half in elements:
        if half.textual content:
            display_markdown(half.textual content)
            proceed
        assert (sdk_image := half.as_image())
        assert (picture := sdk_image._pil_image)
        display_image(picture)

    return picture


def get_retrier() -> tenacity.Retrying:
    return tenacity.Retrying(
        cease=tenacity.stop_after_attempt(7),
        wait=tenacity.wait_incrementing(begin=10, increment=1),
        retry=should_retry_request,
        reraise=True,
    )


def should_retry_request(retry_state: tenacity.RetryCallState) -> bool:
    if not retry_state.final result:
        return False
    err = retry_state.final result.exception()
    if not isinstance(err, ClientError):
        return False
    print(f"❌ ClientError {err.code}: {err.message}")

    retry = False
    match err.code:
        case 400 if err.message isn't None and " strive once more " in err.message:
            # Workshop: Cloud Storage accessed for the primary time (service agent provisioning)
            retry = True
        case 429:
            # Workshop: momentary challenge with 1 QPM quota
            retry = True
    print(f"🔄 Retry: {retry}")

    return retry


def display_markdown(markdown: str) -> None:
    IPython.show.show(IPython.show.Markdown(markdown))


def display_image(picture: PIL_Image) -> None:
    IPython.show.show(picture)

🖼️ Vermögen

Definieren wir die Vermögenswerte für die Reise unseres Charakters und die Funktionen, sie zu verwalten:

import enum
from collections.abc import Sequence
from dataclasses import dataclass


class AssetId(enum.StrEnum):
    ARCHIVE = "0_archive"
    ROBOT = "1_robot"
    MOUNTAINS = "2_mountains"
    VALLEY = "3_valley"
    FOREST = "4_forest"
    CLEARING = "5_clearing"
    ASCENSION = "6_ascension"
    SUMMIT = "7_summit"
    BRIDGE = "8_bridge"
    HAMMOCK = "9_hammock"


@dataclass
class Asset:
    id: str
    source_ids: Sequence(str)
    immediate: str
    pil_image: PIL_Image


class Property(dict(str, Asset)):
    def set_asset(self, asset: Asset) -> None:
        # Word: This replaces any present asset (if wanted, add guardrails to auto-save|preserve all variations)
        self(asset.id) = asset


def generate_image(source_ids: Sequence(str), immediate: str, new_id: str = "") -> None:
    sources = (property(source_id).pil_image for source_id in source_ids)
    immediate = immediate.strip()
    picture = generate_content(sources, immediate)
    if picture and new_id:
        property.set_asset(Asset(new_id, source_ids, immediate, picture))


property = Property()

📦 Referenzarchiv

Wir können jetzt unser Referenzarchiv holen und es zu unserem ersten Kapital machen: 🔽
import urllib.request

import PIL.Picture
import PIL.ImageOps

ARCHIVE_URL = "https://storage.googleapis.com/github-repo/generative-ai/gemini/use-cases/media-generation/consistent_imagery_generation/0_archive.png"


def load_archive() -> None:
    picture = get_image_from_url(ARCHIVE_URL)
    # Preserve unique particulars in 16:9 panorama facet ratio (arbitrary)
    picture = crop_expand_if_needed(picture, 1344, 768)
    property.set_asset(Asset(AssetId.ARCHIVE, (), "", picture))
    display_image(picture)


def get_image_from_url(image_url: str) -> PIL_Image:
    with urllib.request.urlopen(image_url) as response:
        return PIL.Picture.open(response)


def crop_expand_if_needed(picture: PIL_Image, dst_w: int, dst_h: int) -> PIL_Image:
    src_w, src_h = picture.measurement
    if dst_w < src_w or dst_h < src_h:
        crop_l, crop_t = (src_w - dst_w) // 2, (src_h - dst_h) // 2
        picture = picture.crop((crop_l, crop_t, crop_l + dst_w, crop_t + dst_h))
        src_w, src_h = picture.measurement
    if src_w < dst_w or src_h < dst_h:
        off_l, off_t = (dst_w - src_w) // 2, (dst_h - src_h) // 2
        borders = (off_l, off_t, dst_w - src_w - off_l, dst_h - src_h - off_t)
        picture = PIL.ImageOps.increase(picture, borders, fill="white")

    assert picture.measurement == (dst_w, dst_h)
    return picture
load_archive()
Archiv erzeugt im Jahr 2024 mit Imagin 3 vom Autor

💡 Gemini wird das engste Seitenverhältnis des letzten Eingangsbildes erhalten. Folglich haben wir das Archivbild zugeschnitten 1344 × 768 Pixel (in der Nähe 16:9) die ursprünglichen Particulars (keine Neusanierung) zu bewahren und in all unseren zukünftigen Szenen die gleiche Landschaftslösung zu behalten. Gemini kann erzeugen 1024 × 1024 Bilder (1:1) aber auch ihre 16:9Anwesend 9:16Anwesend 4:3Und 3:4 Äquivalente (in Bezug auf Token).

Dieses Archivbild wurde im Juli 2024 mit einer Beta -Model von Imageen 3 erzeugt „Auf weißem Hintergrund, ein kleines handgezogenes Spielzeug aus blauem Roboter. Das Filz ist weich und kuschelig …“. Das Ergebnis sah wirklich intestine aus, aber zu dieser Zeit gab es absolut keinen Determinismus und keine Konsistenz. Infolgedessen conflict dies eine schöne One-Shot-Bildgeneration und der süße kleine Roboter schien für immer verschwunden zu sein …


Versuchen wir, unseren kleinen Roboter zu extrahieren:

source_ids = (AssetId.ARCHIVE)
immediate = "Extract the robotic as is, with out its shadow, changing all the pieces with a stable white fill."

generate_image(source_ids, immediate)
Erzeugt mit Gemini Nano Banane vom Autor

⚠️ Der Roboter wird perfekt extrahiert, aber dies ist im Wesentlichen eine gute Hintergrundentfernung, die viele Modelle durchführen können. Diese Eingabeaufforderung verwendet Begriffe aus Grafiksoftware, während wir jetzt in Bezug auf die Bildkomposition argumentieren können. Es ist auch nicht unbedingt eine gute Idee, traditionelle binäre Masken zu verwenden, da Objektkanten und Schatten bedeutende Particulars zu Formen, Texturen, Positionen und Beleuchtung vermitteln.

Kehren wir zu unserem Archiv zurück, um stattdessen eine fortgeschrittene Extraktion durchzuführen und direkt ein Zeichenblatt zu erzeugen…


🪄 Charakterblatt

Gemini hat ein räumliches Verständnis und kann unterschiedliche Ansichten bieten und gleichzeitig visuelle Merkmale bewahren. Lassen Sie uns ein Vorder-/Rück-/Rück -Charakterblatt erzeugen und, wenn unser kleiner Roboter eine Reise unternimmt, gleichzeitig einen Rucksack hinzufügen:

source_ids = (AssetId.ARCHIVE)
immediate = """
- Scene: Robotic character sheet.
- Left: Entrance view of the extracted robotic.
- Proper: Again view of the extracted robotic (seamless again).
- The robotic wears a similar small, brown-felt backpack, with a tiny polished-brass buckle and easy straps in each views. The backpack straps are seen in each views.
- Background: Pure white.
- Textual content: On the highest, caption the picture "ROBOT CHARACTER SHEET" and, on the underside, caption the views "FRONT VIEW" and "BACK VIEW".
"""
new_id = AssetId.ROBOT

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 Ein paar Bemerkungen:

  • Die Eingabeaufforderung beschreibt die Szene in Bezug auf Komposition, wie in Medienstudios häufig verwendet.
  • Wenn wir aufeinanderfolgende Generationen versuchen, sind sie konsistent, wobei alle Roboterfunktionen erhalten sind.
  • Unsere Eingabeaufforderung beschreibt einige Aspekte des Rucksacks, aber wir werden für alles, was nicht spezifiziert ist, etwas andere Rucksäcke erhalten.
  • Der Einfachheit halber haben wir den Rucksack direkt im Charakterblatt hinzugefügt, aber in einer realen Produktionspipeline würden wir ihn wahrscheinlich zu einem separaten Zubehörblatt machen.
  • Um genau die Rucksackform und das Design zu steuern, konnten wir auch ein Referenzfoto verwenden und „den Rucksack in eine stilisierte Filzversion verwandeln“.

Dieses neue Kapital kann jetzt als Designreferenz in unseren zukünftigen Bildgenerationen dienen.


✨ Erste Szene

Beginnen wir mit einer Berglandschaft:

source_ids = (AssetId.ROBOT)
immediate = """
- Picture 1: Robotic character sheet.
- Scene: Macro pictures of a fantastically crafted miniature diorama.
- Background: Gentle-focus of a panoramic vary of interspersed, dome-like felt mountains, in varied shades of medium blue/inexperienced, with curvy white snowcaps, extending over the whole horizon.
- Foreground: Within the bottom-left, the robotic stands on the sting of a medium-gray felt cliff, seen from a 3/4 again angle, searching over a sea of clouds (made from white cotton).
- Lighting: Studio, clear and comfortable.
"""
new_id = AssetId.MOUNTAINS

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 Die Bergform wird als „Kuppel-ähnlich“ angegeben, sodass unser Charakter später auf einem der Gipfel stehen kann.

Es ist wichtig, einige Zeit in dieser ersten Szene zu verbringen, da es in einem kaskadierenden Effekt den Gesamtaussehen unserer Geschichte definieren wird. Nehmen Sie sich etwas Zeit, um die Eingabeaufforderung zu verfeinern, oder versuchen Sie es ein paar Mal, um die beste Variation zu erhalten.

Von nun an werden unsere Generationsinputs sowohl das Charakterblatt als auch eine Referenzszene sein…


✨ Folge Szenen

Lassen Sie uns den Roboter in ein Tal räumen:

source_ids = (AssetId.ROBOT, AssetId.MOUNTAINS)
immediate = """
- Picture 1: Robotic character sheet.
- Picture 2: Earlier scene.
- The robotic has descended from the cliff to a grey felt valley. It stands within the middle, seen straight from the again. It's holding/studying a felt map with outstretched arms.
- Giant clean, spherical, felt rocks in varied beige/grey shades are seen on the perimeters.
- Background: The distant mountain vary. A skinny layer of clouds obscures its base and the tip of the valley.
- Lighting: Golden hour mild, comfortable and subtle.
"""
new_id = AssetId.VALLEY

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 Ein paar Hinweise:

  • Die bereitgestellten Spezifikationen zu unseren Eingabebildern ("Picture 1:…"Anwesend "Picture 2:…") sind wichtig. Ohne sie könnte sich „der Roboter“ auf einen der 3 Roboter in den Eingangsbildern beziehen (2 im Zeichenblatt, 1 in der vorherigen Szene). Mit ihnen geben wir an, dass es der gleiche Roboter ist. Im Falle von Verwirrung können wir genauer sein mit "the (entity) from picture (quantity)".
  • Auf der anderen Seite, da wir keine genaue Beschreibung des Tals geliefert haben, werden aufeinanderfolgende Anfragen unterschiedliche, interessante und kreative Ergebnisse liefern (wir können unseren Favoriten auswählen oder genauer für den Determinismus genauer machen).
  • Hier haben wir auch eine andere Beleuchtung getestet, die die gesamte Szene erheblich verändert.

Dann können wir uns in diese Szene bewegen:

source_ids = (AssetId.ROBOT, AssetId.VALLEY)
immediate = """
- Picture 1: Robotic character sheet.
- Picture 2: Earlier scene.
- The robotic goes on and faces a dense, infinite forest of straightforward, large, skinny timber, that fills the whole background.
- The timber are comprised of varied shades of sunshine/medium/darkish inexperienced felt.
- The robotic is on the proper, seen from a 3/4 rear angle, not holding the map, with each palms clasped to its ears in despair.
- On the left & proper backside sides, rocks (just like picture 2) are partially seen.
"""
new_id = AssetId.FOREST

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 von Interesse:

  • Wir könnten den Charakter positionieren, seinen Standpunkt ändern und seine Arme sogar für mehr Ausdrucksfähigkeit „animieren“.
  • Die Präzision „Nicht mehr hält die Karte mehr“ verhindert, dass das Modell versucht, es von der vorherigen Szene aus sinnvoller Weise zu verhindern (z. B. ließ der Roboter die Karte auf den Boden fallen).
  • Wir haben keine Beleuchtungsdetails angegeben: Die Beleuchtungsquelle, Qualität und Richtung wurden von der vorherigen Szene aufbewahrt.

Gehen wir durch den Wald:

source_ids = (AssetId.ROBOT, AssetId.FOREST)
immediate = """
- Picture 1: Robotic character sheet.
- Picture 2: Earlier scene.
- The robotic goes by the dense forest and emerges right into a clearing, pushing apart two tree trunks.
- The robotic is within the middle, now seen from the entrance view.
- The bottom is made from inexperienced felt, with flat patches of white felt snow. Rocks are not seen.
"""
new_id = AssetId.CLEARING

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 Wir haben den Boden geändert, aber keine zusätzlichen Particulars für die Aussicht und den Wald geliefert: Das Modell wird im Allgemeinen die meisten Bäume bewahren.

Jetzt, da die Valley-Forest-Sequenz vorbei ist, können wir in die Berge reisen und die ursprüngliche Bergszene nutzen, um in diese Umgebung zurückzukehren:

source_ids = (AssetId.ROBOT, AssetId.MOUNTAINS)
immediate = """
- Picture 1: Robotic character sheet.
- Picture 2: Earlier scene.
- Shut-up of the robotic now climbing the height of a medium-green mountain and reaching its summit.
- The mountain is correct within the middle, with the robotic on its left slope, seen from a 3/4 rear angle.
- The robotic has each toes on the mountain and is utilizing two felt ice axes (brown handles, grey heads), reaching the snowcap.
- Horizon: The distant mountain vary.
"""
new_id = AssetId.ASCENSION

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 Die aus dem verschwommene Hintergrund abgeleitete Berg-Nahaufnahme ist ziemlich beeindruckend.

Lassen Sie uns zum Gipfel klettern:

source_ids = (AssetId.ROBOT, AssetId.ASCENSION)
immediate = """
- Picture 1: Robotic character sheet.
- Picture 2: Earlier scene.
- The robotic reaches the highest and stands on the summit, seen within the entrance view, in close-up.
- It's not holding the ice axes, that are planted upright within the snow on both sides.
- It has each arms raised in signal of victory.
"""
new_id = AssetId.SUMMIT

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 Dies ist eine logische Comply with-up, aber auch eine schöne, andere Sichtweise.

Versuchen wir nun etwas anderes, um die Szene erheblich umzusetzen:

source_ids = (AssetId.ROBOT, AssetId.SUMMIT)
immediate = """
- Picture 1: Robotic character sheet.
- Picture 2: Earlier scene.
- Take away the ice axes.
- Transfer the middle mountain to the left fringe of the picture and add a barely taller medium-blue mountain to the proper edge.
- Droop a stylized felt bridge between the 2 mountains: Its deck is made from thick felt planks in varied wooden shades.
- Place the robotic on the middle of the bridge with one arm pointing towards the blue mountain.
- View: Shut-up.
"""
new_id = AssetId.BRIDGE

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 von Interesse:

  • Diese crucial Eingabeaufforderung komponiert die Szene in Bezug auf Aktionen. Es ist manchmal einfacher als Beschreibungen.
  • Ein neuer Berg wird wie angewiesen hinzugefügt und ist sowohl unterschiedlich als auch konsequent.
  • Die Brücke verbindet die Gipfel auf sehr believable Weise und scheint den Gesetzen der Physik zu befolgen.
  • Die Anweisung „Entfernen Sie die Eispunkte“ befinden sich aus einem bestimmten Grund. Ohne es ist es so, als würden wir dazu veranlasst, „alles zu tun, was Sie können, mit den Eispials aus der vorherigen Szene: Lassen Sie sie dort, wo sie sind, lassen Sie den Roboter nicht ohne sie gehen oder irgendetwas anderes“, was zu zufälligen Ergebnissen führt.
  • Es ist auch möglich, den Roboter auf die Brücke zu bringen, die von der Seite aus gesehen werden (was wir noch nie zuvor erzeugt haben), aber es ist schwierig, es konsequent von hyperlinks nach rechts zu laufen. Das Hinzufügen von linken und rechten Ansichten im Zeichenblatt sollte dies beheben.

Generieren wir eine letzte Szene und lassen Sie den Roboter wohlverdient werden:

source_ids = (AssetId.ROBOT, AssetId.BRIDGE)
immediate = """
- Picture 1: Robotic character sheet.
- Picture 2: Earlier scene.
- The robotic is sleeping peacefully (each eyes become a "closed" state), in a cushty brown-and-tan tartan hammock that has changed the bridge.
"""
new_id = AssetId.HAMMOCK

generate_image(source_ids, immediate, new_id)
Erzeugt mit Gemini Nano Banane vom Autor

💡 von Interesse:

  • Diesmal ist die Eingabeaufforderung beschreibend und funktioniert sowie die vorherige crucial Eingabeaufforderung.
  • Die Bridge-Hammock-Transformation ist wirklich schön und bewahrt die Anhänge auf den Berggipfeln.
  • Die Roboter -Transformation ist ebenfalls beeindruckend, da sie in dieser Place noch nicht gesehen wurde.
  • Die geschlossenen Augen sind das schwierigste Element, um konsequent zu erhalten (können möglicherweise ein paar Versuche erfordern), wahrscheinlich weil wir viele verschiedene Transformationen gleichzeitig ansammeln (und die Aufmerksamkeit des Modells verwässern). Für die vollständige Kontrolle und deterministischere Ergebnisse können wir uns auf signifikante Änderungen gegenüber iterativen Schritten konzentrieren oder verschiedene Charakterblätter im Voraus erstellen.

Wir haben unsere Geschichte mit 9 neuen konsequenten Bildern illustriert! Machen wir einen Schritt zurück, um zu verstehen, was wir gebaut haben …


🗺️ Graph Visualisierung

Wir haben jetzt eine Sammlung von Picture-Property, von Archiven bis hin zu brandneu generierten Vermögenswerten.

Fügen wir eine Datenvisualisierung hinzu, um die auszuführenden Schritte besser zu machen.


🔗 Regie Graph

Unsere neuen Vermögenswerte sind alle verwandt, die durch eine oder mehrere „generierte“ Hyperlinks verbunden sind. Aus Sicht der Datenstruktur handelt es sich um eine gerichtete Grafik.

Wir können das entsprechende gerichtete Diagramm mit dem erstellen networkx Bibliothek:

import networkx as nx


def build_graph(property: Property) -> nx.DiGraph:
    graph = nx.DiGraph(property=property)
    # Nodes
    for asset in property.values():
        graph.add_node(asset.id, asset=asset)
    # Edges
    for asset in property.values():
        for source_id in asset.source_ids:
            graph.add_edge(source_id, asset.id)
    return graph


asset_graph = build_graph(property)
print(asset_graph)
DiGraph with 10 nodes and 16 edges
Lassen Sie uns das am häufigsten verwendete Vermögenswert in der Mitte platzieren und die anderen Vermögenswerte anzeigen: 🔽
import matplotlib.pyplot as plt


def display_basic_graph(graph: nx.Graph) -> None:
    pos = compute_node_positions(graph)
    coloration = "#4285F4"
    choices = dict(
        node_color=coloration,
        edge_color=coloration,
        arrowstyle="wedge",
        with_labels=True,
        font_size="small",
        bbox=dict(ec="black", fc="white", alpha=0.7),
    )
    nx.draw(graph, pos, **choices)
    plt.present()


def compute_node_positions(graph: nx.Graph) -> dict(str, tuple(float, float)):
    # Put probably the most related node within the middle
    center_node = most_connected_node(graph)
    edge_nodes = set(graph) - {center_node}
    pos = nx.circular_layout(graph.subgraph(edge_nodes))
    pos(center_node) = (0.0, 0.0)
    return pos


def most_connected_node(graph: nx.Graph) -> str:
    if not graph.nodes():
        return ""
    centrality_by_id = nx.degree_centrality(graph)
    return max(centrality_by_id, key=lambda s: centrality_by_id.get(s, 0.0))
display_basic_graph(asset_graph)
Generiert vom Autor aus dem Notizbuch des Artikels

Das ist eine korrekte Zusammenfassung unserer verschiedenen Schritte. Es wäre schön, wenn wir auch unsere Vermögenswerte visualisieren könnten …


🌟 Asset -Diagramm

Fügen wir Customized hinzu matplotlib Funktionen, um die Graphenknoten mit den Vermögenswerten visuell ansprechender zu machen: 🔽
import typing
from collections.abc import Iterator
from io import BytesIO
from pathlib import Path

import PIL.Picture
import PIL.ImageDraw
from google.genai.sorts import PIL_Image
from matplotlib.axes import Axes
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.determine import Determine
from matplotlib.picture import AxesImage
from matplotlib.patches import Patch
from matplotlib.textual content import Annotation
from matplotlib.transforms import Bbox, TransformedBbox


@enum.distinctive
class ImageFormat(enum.StrEnum):
    # Matches PIL.Picture.Picture.format
    WEBP = enum.auto()
    PNG = enum.auto()
    GIF = enum.auto()


def yield_generation_graph_frames(
    graph: nx.DiGraph,
    animated: bool,
) -> Iterator(PIL_Image):
    def get_fig_ax() -> tuple(Determine, Axes):
        issue = 1.0
        figsize = (16 * issue, 9 * issue)
        fig, ax = plt.subplots(figsize=figsize)
        fig.tight_layout(pad=3)
        handles = (
            Patch(coloration=COL_OLD, label="Archive"),
            Patch(coloration=COL_NEW, label="Generated"),
        )
        ax.legend(handles=handles, loc="decrease proper")
        ax.set_axis_off()
        return fig, ax

    def prepare_graph() -> None:
        arrows = nx.draw_networkx_edges(graph, pos, ax=ax)
        for arrow in arrows:
            arrow.set_visible(False)

    def get_box_size() -> tuple(float, float):
        xlim_l, xlim_r = ax.get_xlim()
        ylim_t, ylim_b = ax.get_ylim()
        issue = 0.08
        box_w = (xlim_r - xlim_l) * issue
        box_h = (ylim_b - ylim_t) * issue
        return box_w, box_h

    def add_axes() -> Axes:
        xf, yf = tr_figure(pos(node))
        xa, ya = tr_axes((xf, yf))
        x_y_w_h = (xa - box_w / 2.0, ya - box_h / 2.0, box_w, box_h)
        a = plt.axes(x_y_w_h)
        a.set_title(
            asset.id,
            loc="middle",
            backgroundcolor="#FFF8",
            fontfamily="monospace",
            fontsize="small",
        )
        a.set_axis_off()
        return a

    def draw_box(coloration: str, picture: bool) -> AxesImage:
        if picture:
            consequence = pil_image.copy()
        else:
            consequence = PIL.Picture.new("RGB", image_size, coloration="white")
        xy = ((0, 0), image_size)
        # Draw field define
        draw = PIL.ImageDraw.Draw(consequence)
        draw.rounded_rectangle(xy, box_r, define=coloration, width=outline_w)
        # Make all the pieces outdoors the field define clear
        masks = PIL.Picture.new("L", image_size, 0)
        draw = PIL.ImageDraw.Draw(masks)
        draw.rounded_rectangle(xy, box_r, fill=0xFF)
        consequence.putalpha(masks)
        return a.imshow(consequence)

    def draw_prompt() -> Annotation:
        textual content = f"Immediate:n{asset.immediate}"
        margin = 2 * outline_w
        image_w, image_h = image_size
        bbox = Bbox(((0, margin), (image_w - margin, image_h - margin)))
        clip_box = TransformedBbox(bbox, a.transData)
        return a.annotate(
            textual content,
            xy=(0, 0),
            xytext=(0.06, 0.5),
            xycoords="axes fraction",
            textcoords="axes fraction",
            verticalalignment="middle",
            fontfamily="monospace",
            fontsize="small",
            linespacing=1.3,
            annotation_clip=True,
            clip_box=clip_box,
        )

    def draw_edges() -> None:
        STYLE_STRAIGHT = "arc3"
        STYLE_CURVED = "arc3,rad=0.15"
        for mum or dad in graph.predecessors(node):
            edge = (mum or dad, node)
            coloration = COL_NEW if property(mum or dad).immediate else COL_OLD
            fashion = STYLE_STRAIGHT if center_node in edge else STYLE_CURVED
            nx.draw_networkx_edges(
                graph,
                pos,
                (edge),
                width=2,
                edge_color=coloration,
                fashion="dotted",
                ax=ax,
                connectionstyle=fashion,
            )

    def get_frame() -> PIL_Image:
        canvas = typing.forged(FigureCanvasAgg, fig.canvas)
        canvas.draw()
        image_size = canvas.get_width_height()
        image_bytes = canvas.buffer_rgba()
        return PIL.Picture.frombytes("RGBA", image_size, image_bytes).convert("RGB")

    COL_OLD = "#34A853"
    COL_NEW = "#4285F4"
    property = graph.graph("property")
    center_node = most_connected_node(graph)
    pos = compute_node_positions(graph)
    fig, ax = get_fig_ax()
    prepare_graph()
    box_w, box_h = get_box_size()
    tr_figure = ax.transData.remodel  # Knowledge → show coords
    tr_axes = fig.transFigure.inverted().remodel  # Show → determine coords

    for node, knowledge in graph.nodes(knowledge=True):
        if animated:
            yield get_frame()
        # Edges and sub-plot
        asset = knowledge("asset")
        pil_image = asset.pil_image
        image_size = pil_image.measurement
        box_r = min(image_size) * 25 / 100  # Radius for rounded rect
        outline_w = min(image_size) * 5 // 100
        draw_edges()
        a = add_axes()  # a is utilized in sub-functions
        # Immediate
        if animated and asset.immediate:
            field = draw_box(COL_NEW, picture=False)
            immediate = draw_prompt()
            yield get_frame()
            field.set_visible(False)
            immediate.set_visible(False)
        # Generated picture
        coloration = COL_NEW if asset.immediate else COL_OLD
        draw_box(coloration, picture=True)

    plt.shut()
    yield get_frame()


def draw_generation_graph(
    graph: nx.DiGraph,
    format: ImageFormat,
) -> BytesIO:
    frames = checklist(yield_generation_graph_frames(graph, animated=False))
    assert len(frames) == 1
    body = frames(0)

    params: dict(str, typing.Any) = dict()
    match format:
        case ImageFormat.WEBP:
            params.replace(lossless=True)

    image_io = BytesIO()
    body.save(image_io, format, **params)

    return image_io


def draw_generation_graph_animation(
    graph: nx.DiGraph,
    format: ImageFormat,
) -> BytesIO:
    frames = checklist(yield_generation_graph_frames(graph, animated=True))
    assert 1 <= len(frames)

    if format == ImageFormat.GIF:
        # Dither all frames with the identical palette to optimize the animation
        # The animation is cumulative, so most colours are within the final body
        technique = PIL.Picture.Quantize.MEDIANCUT
        palettized = frames(-1).quantize(technique=technique)
        frames = (body.quantize(technique=technique, palette=palettized) for body in frames)

    # The animation might be performed in a loop: begin biking with probably the most full body
    first_frame = frames(-1)
    next_frames = frames(:-1)
    INTRO_DURATION = 3000
    FRAME_DURATION = 1000
    durations = (INTRO_DURATION) + (FRAME_DURATION) * len(next_frames)
    params: dict(str, typing.Any) = dict(
        save_all=True,
        append_images=next_frames,
        period=durations,
        loop=0,
    )
    match format:
        case ImageFormat.GIF:
            params.replace(optimize=False)
        case ImageFormat.WEBP:
            params.replace(lossless=True)

    image_io = BytesIO()
    first_frame.save(image_io, format, **params)

    return image_io


def display_generation_graph(
    graph: nx.DiGraph,
    format: ImageFormat | None = None,
    animated: bool = False,
    save_image: bool = False,
) -> None:
    if format is None:
        format = ImageFormat.WEBP if running_in_colab_env else ImageFormat.PNG
    if animated:
        image_io = draw_generation_graph_animation(graph, format)
    else:
        image_io = draw_generation_graph(graph, format)

    image_bytes = image_io.getvalue()
    IPython.show.show(IPython.show.Picture(image_bytes))

    if save_image:
        stem = "graph_animated" if animated else "graph"
        Path(f"./{stem}.{format.worth}").write_bytes(image_bytes)

Wir können jetzt unser Generationsdiagramm anzeigen:

display_generation_graph(asset_graph)
Generiert vom Autor aus dem Notizbuch des Artikels

🚀 Herausforderung abgeschlossen

Wir haben es geschafft, mit Nano Banane eine vollständige Reihe neuer konsequenter Bilder zu generieren und auf dem Weg ein paar Dinge zu lernen:

  • Bilder beweisen erneut, dass sie mehr als tausend Worte wert sind: Es ist jetzt viel einfacher, neue Bilder aus vorhandenen und einfachen Anweisungen zu generieren.
  • Wir können Bilder nur in Bezug auf Komposition erstellen oder bearbeiten (lassen Sie uns alle künstlerische Direktoren werden).
  • Wir können beschreibende oder crucial Anweisungen verwenden.
  • Das räumliche Verständnis des Modells ermöglicht 3D -Manipulationen.
  • Wir können Textual content in unseren Ausgängen (Zeichenblatt) hinzufügen und auch auf Textual content in unseren Eingängen (Vorder-/Rückansichten) verweisen.
  • Konsistenz kann auf sehr unterschiedlichen Ebenen aufbewahrt werden: Charakter, Szene, Textur, Beleuchtung, Kamerawinkel/Typ…
  • Der Erzeugungsprozess kann weiterhin iterativ sein, aber es fühlt sich wie 10x-100x schneller an, um besser als mit Hop-für-Ergebnisse zu erzielen.
  • Es ist jetzt möglich, unseren Archiven neues Leben einzuhauchen!

Mögliche nächste Schritte:

  • Der Prozess, dem wir befolgt werden, ist im Wesentlichen eine Generationspipeline. Es kann für die Automatisierung industrialisiert werden (z. B. das Ändern eines Knotens regeneriert seine Nachkommen) oder für die Erzeugung verschiedener paralleler Variationen (z. B. können derselbe Bildersatz für verschiedene Ästhetik, Publikum oder Simulationen generiert werden).
  • Aus Einfachheit und Erforschung sind die Eingabeaufforderungen absichtlich einfach. In einer Produktionsumgebung könnten sie eine feste Struktur mit einem systematischen Satz von Parametern haben.
  • Wir haben Szenen wie in einem Fotostudio beschrieben. Praktisch jeder andere vorstellbare künstlerische Stil ist möglich (fotorealistisch, abstrakt, second…).
  • Unsere Vermögenswerte könnten autark gemacht werden, indem Eingabeaufforderungen und Vorfahren in den Bildmetadaten (z. B. in PNG-Stücken) gespeichert werden, um einen vollständigen lokalen Speicher und Abruf zu ermöglichen (keine Datenbank und keine mehr verlorenen Eingabeaufforderungen mehr!). Weitere Informationen finden Sie im Abschnitt „Asset Metadata“ im Pocket book (Hyperlink unten).

Lassen Sie uns als Bonus mit einer animierten Model unserer Reise enden, wobei das Generationsdiagramm auch einen Blick auf unsere Anweisungen zeigt:

display_generation_graph(asset_graph, animated=True)
Generiert vom Autor aus dem Notizbuch des Artikels

➕ Mehr!

Willst du tiefer gehen?

Danke fürs Lesen. Ich freue mich darauf zu sehen, was Sie erstellen!

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert