[Python] 362 OpenAI DALL-E 3による画像生成

[Mac M2 Pro 12CPU, Ventura 13.6, Python 3.10.4]

DALL-E 3からAPIで使えるようになったので、早速試してみました。

生駒山近景を出力したところ存在しないロープウェイを含む景色でした。2回目は信仰の山らしく祠の画像でした。六甲山も出してみました。

どうも画のタッチが好みではありません。そこら辺を調整できれば、使い出がありそうです。

# openaiライブラリを導入済みの場合はアップグレードする(旧Verにはopenai.OpenAIがない)
pip install --upgrade openai
import os
from openai import OpenAI

# 環境変数からAPIキーを取得
api_key = os.getenv('CHATGPT_API_KEY')
if not api_key:
    raise ValueError("OpenAI API Keyが環境変数に設定されていません")

# APIキーを設定
os.environ["OPENAI_API_KEY"] = api_key

client = OpenAI()

response = client.images.generate(
  model="dall-e-3",
  prompt="大阪府・奈良県にある生駒山の近景",
  size="1024x1024",
  quality="standard",
  n=1,
)

image_url = response.data[0].url
print(image_url)
生駒山近景1
生駒山近景2
六甲山近景

[C++] 355 ChatGPTアプリの製作 その40 GPT-4 Turbo with visionへの対応

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 15.0.0]

自製ChatGPTアプリをGPT-4 Turbo with visionに対応させました。

画像のURLを送ると内容を読み取り、プロンプトに対応します。

漢字、ひらがな、カタカナなど非ラテン文字は読めません。これらが画像に含まれているとレスポンスしなくなるので、モザイクを掛けるなり前処理が必要です。

プログラミングでUIについて質問する際に画像を使うとやりやすいです。特に見てほしいところを色線で囲むなど、工夫を入れるとより深い分析をしてくれます。ここまでできるとプログラミングスクールにとどまらず教育業界全体が相当な危機でしょう。

GPT-4までは心強いパートナーという感じでしたが、with visionになって凄まじい眼力を身に付け、いささか脅威を覚えるようになりました。

今のAIがどのような状況になっているのか、もっと周知しないとヤバい気がします。

[JavaScript] 20 Adobe XDのアイテムデータを取得するプラグイン作成 その6 Pythonスクリプトとの統合 / GPT-4 Turbo

[Mac M2 Pro 12CPU, Ventura 13.6]

ChatGPTがGPT-4 Turboになってトークン数に余裕ができたので、早速仕事をしてもらいました。プラグインのmain.jsにtxtTojson.pyの内容を取り込みました。

自力でもある程度プログラミングできるレベルであれば、ChatGPTによる恩恵は絶大だと思います。

const fs = require("uxp").storage.localFileSystem;

async function myCommand(selection) {
    let items_list = [];
    selection.items.forEach(function (value, index) {
        // ここでPythonスクリプトの処理に相当するデータ抽出を行う
        const name = value.name;
        const bounds = value.globalBounds; // globalBoundsオブジェクトを取得
        const x = bounds.x;
        const y = bounds.y;
        const w = bounds.width; // globalBoundsからwidthを取得
        const h = bounds.height; // globalBoundsからheightを取得
        items_list.push({ name, bounds: [x, y, w, h] });
    });

    // JSONオブジェクトを作成
    const json_obj = {};
    items_list.forEach(item => {
        json_obj[item.name] = item.bounds;
    });

    // JSON文字列に変換
    const json_str = JSON.stringify(json_obj, null, 2);

    // ファイルを保存する
    const file = await fs.getFileForSaving("output.json", { types: ["json"] });
    await file.write(json_str);
}

module.exports = {
    commands: {
        GetItemXY: myCommand
    }
};
AdobeXDプラグインを使って2つの方法でアイテムデータを取得し、比較しています
1.プラグインのmain.jsでアイテムデータをtxtファイル化し、txtTojson.pyでjsonファイルに変換する
2.プラグインのmain.jsでアイテムデータをjsonファイル化する

1の方法では成功しますが、2の方法ではデータの欠損が生じます。

以下にそれぞれのスクリプトを記述しますので、これらを参考に2の方法のmain.jsを修正してください

1の方法
-- main.js
const fs = require("uxp").storage.localFileSystem;

async function myCommand(selection) {
    let items_list = "";
    var num = 1;
    selection.items.forEach(function(value){
        if (num < selection.items.length){
            items_list += value + ";";
        } else {
            items_list += value;
        }
        num += 1;
    });

    // ファイルを保存する
    const file = await fs.getFileForSaving("output.txt", { types: ["txt"] });
    await file.write(items_list);
}

module.exports = {
    commands: {
        GetItemXY: myCommand
    }
};
--

-- txtTojson.py
import json

with open("items.txt", "r") as tf:
    items_list = tf.read().replace("\n","").split(';')
    
# print(items_list)
# print(len(items_list))

# listからデータ抽出し、JSON文字列作成
json_str = ""
num = 0
for item in items_list:
    name = item.split("'")[1]
    print(name + "\n")
    
    xy = (item.split("global X,Y:")[1]).split("parent")[0]
    x = xy.split(",")[0]
    y = xy.split(",")[1]
    print(x + "\n")
    print(y + "\n")
    
    wh = (item.split("{")[1]).split("global")[0]
    w = (wh.split("width:")[1]).split(", height")[0]
    h = wh.split("height:")[1]
    print(w + "\n")
    print(h + "\n")
    
    if num == 0:
        json_str += "{\"" + name + "\"" + ":[" + x + ", " + y + ", " + w + ", " + h + "],\n"
    elif num < len(items_list) -1:
        json_str += "\"" + name + "\"" + ":[" + x + ", " + y + ", " + w + ", " + h + "],\n"
    else:
        json_str += "\"" + name + "\"" + ":[" + x + ", " + y + ", " + w + ", " + h + "]}"

    print(json_str + "\n")
    
    num += 1

# JSONファイル作成
file = open('items.json', mode='w')
file.write(json_str.replace(" ",""))
file.close()
--

-- items.json
{"input":{"x":20,"y":23,"w":14,"h":18},
"長方形11":{"x":6,"y":50,"w":30,"h":30},
"inputLine":{"x":40,"y":30,"w":370,"h":130},
"showBtn":{"x":40,"y":10,"w":370,"h":20},
"showBtn":{"x":20,"y":165,"w":160,"h":30},
"長方形7":{"x":20,"y":200,"w":460,"h":320},
"Codeカウント":{"x":196,"y":165,"w":40,"h":30},
"Codeカウント":{"x":252,"y":165,"w":60,"h":30},
"showBtn":{"x":327,"y":165,"w":28,"h":30},
"Codeカウント":{"x":340,"y":525,"w":40,"h":20},
"長方形9":{"x":20,"y":550,"w":460,"h":82},
"showBtn":{"x":370,"y":165,"w":28,"h":30},
"showBtn":{"x":387,"y":525,"w":26,"h":20}}
--

2の方法
-- main.js
const fs = require("uxp").storage.localFileSystem;

async function myCommand(selection) {
    let items_list = [];
    selection.items.forEach(function (value) {
        // ここでPythonスクリプトの処理に相当するデータ抽出を行う
        const name = value.name;
        const x = value.globalBounds.x;
        const y = value.globalBounds.y;
        const w = value.width;
        const h = value.height;
        items_list.push({ name, bounds: [x, y, w, h] });
    });

    // JSONオブジェクトを作成
    const json_obj = {};
    items_list.forEach(item => {
        json_obj[item.name] = item.bounds;
    });

    // JSON文字列に変換
    const json_str = JSON.stringify(json_obj, null, 2);

    // ファイルを保存する
    const file = await fs.getFileForSaving("output.json", { types: ["json"] });
    await file.write(json_str);
}

module.exports = {
    commands: {
        GetItemXY: myCommand
    }
};
--

-- output.json(データが欠損している)
{
  "長方形 7": [
    20,
    200,
    460,
    320
  ],
  "showBtn": [
    327,
    165,
    null,
    null
  ],
  "長方形 9": [
    20,
    550,
    460,
    82
  ],
  "Codeカウント": [
    252,
    165,
    null,
    null
  ]
}
--
2の方法でデータの欠損が生じている原因は、同じ名前のアイテムが複数存在する場合、JSONオブジェクトにおいて後から追加されたアイテムで以前のアイテムのデータが上書きされてしまうためです。JSONオブジェクトのキーは一意でなければならないため、同じ名前のアイテムがあると問題が発生します。

この問題を解決するためには、アイテムの名前が重複しないように一意のキーを生成するか、アイテムのリストを配列としてJSONに保存する必要があります。以下に修正したスクリプトを示します。

```javascript
const fs = require("uxp").storage.localFileSystem;

async function myCommand(selection) {
    let items_list = [];
    selection.items.forEach(function (value, index) {
        // ここでPythonスクリプトの処理に相当するデータ抽出を行う
        const name = value.name;
        const x = value.globalBounds.x;
        const y = value.globalBounds.y;
        const w = value.width;
        const h = value.height;
        // 同じ名前のアイテムがある場合に備えて、インデックスを名前に追加
        const uniqueName = `${name}_${index}`;
        items_list.push({ name: uniqueName, bounds: [x, y, w, h] });
    });

    // JSONオブジェクトを作成
    const json_obj = {};
    items_list.forEach(item => {
        json_obj[item.name] = item.bounds;
    });

    // JSON文字列に変換
    const json_str = JSON.stringify(json_obj, null, 2);

    // ファイルを保存する
    const file = await fs.getFileForSaving("output.json", { types: ["json"] });
    await file.write(json_str);
}

module.exports = {
    commands: {
        GetItemXY: myCommand
    }
};
```

この修正により、各アイテムに一意の名前が付けられ、JSONオブジェクトに保存される際にデータの上書きが発生しなくなります。ただし、この方法では元のアイテム名とインデックスが結合された形で保存されるため、元の名前だけを知りたい場合は後処理が必要になるかもしれません。それを避けるためには、アイテムを配列として保存する方法も考えられます。

[C++] 354 ChatGPTアプリの製作 その39 GPT-4 Turboへの対応

[Mac M2 Pro 12CPU, MacOS Ventura 13.6, clang 15.0.0]

自製ChatGPTアプリを本日11/7未明よりサービス開始となったGPT-4 Turboに対応させました。

たまに英語で回答が返ってくるため、最初の役割設定(role : sys)で日本語回答を必須にしました。

トレーニングデータは23年4月までですから、AppleのvisionOSに関する情報はカバーしていません。

トークンの上限がGPT-4の8,192から128,000になりました。ChatGPTでのやり取りが途切れることも大分減りそうです。また料金はGPT-4の3分の1なので、ある程度気にせず利用できます。

[JavaScript] 19 Adobe XDのアイテムデータを取得するプラグイン作成 その5 作業手順 / UXP for Adobe XD / Pythonでjson化

[Mac M2 Pro 12CPU, Ventura 13.6]

作業手順をまとめておきます。

1.Adobe XDのデザインタブでXY座標を取得したい項目を選択する

2. メニューのプラグインからItemXYGetterを選択する。出力ファイルを指定して実行する。

3.出力されたtxtファイルをtxtTojson.pyで処理し、jsonファイルにする。

時間があればJavaScriptとPythonのスクリプトを統合し、JavaScriptのプラグインだけで処理できるようにしたいです。

※ ItemXYGetterのjsスクリプトの内容等は過去記事で紹介しています。画面左の検索窓で探してみてください。

Pythonスクリプトでjsonファイルに変換

[Swift] 52 BBS閲覧アプリiOS版製作 その6 スレッド表示 / datファイル取得

[Mac M2 Pro 12CPU, Ventura 13.6, iOS 17.0.3, Xcode 15.0]

掲示板サイトからdatファイルをダウンロードするところで、結構時間がかかりました。

ちょっとした手違いで非同期関数が非同期的に動作せず、ダウンロード完了の手前で終わっていました。

今はdatファイルの内容をそのまま表示しています。次はこれをHTMLに加工します。

datファイルはiPhone内のコンテナに保存しています。エディタで内容を確認するため可視ファイルにしたいのですが、今のところできていません。

掲示板名(横線)スレッドタイトル(横線)スレッド内容、を縦に並べている
import SwiftUI

struct datView: View {
    var board: Board?
    var thread: Thread?
    @State var datPath: String = ""
    @State var datContent: String?

    var body: some View {
        VStack(spacing: 8) {
            if let board = board {
                Text(board.name)
                    .font(.system(size: 16))
                Divider() // 横線
            }
            if let thread = thread {
                Text(thread.threadTitle)
                    .font(.system(size: 16))
                Divider()
            }
            // datファイルの内容を表示する
            if let content = datContent {
               Text(content)
            }
            Spacer()
        }
        .padding(.horizontal, 0)
        .padding(.top, 0)
        .padding(.bottom, 0)
        .task {
            if let thread = thread, let board = board {
                datPath = await makeDat(id: thread.threadID, url: board.url)
                datContent = await readDatFile(path: datPath)
                if datContent?.isEmpty ?? true {
                    print("datなし")
                }
            }
        }
        .refreshable {
            if let thread = thread, let board = board {
                datPath = await makeDat(id: thread.threadID, url: board.url)
                datContent = await readDatFile(path: datPath)
                if datContent?.isEmpty ?? true {
                    print("datなし")
                }
            }
        }
    }
}

struct datView_Previews: PreviewProvider {
    static var previews: some View {
        datView()
    }
}
今のところアプリ本体は852KB

[Swift] 51 BBS閲覧アプリiOS版製作 その5 文字列分割 / TestFlight登録 / Vision Pro

[Mac M2 Pro 12CPU, Ventura 13.6, iOS 17.0.3, Xcode 15.0]

手掛けている掲示板のHTMLから取得する文字列はスレッド番号、タイトル、投稿数が結合したものです。これを分割して、Viewに横並びさせました。

このアプリを公開する予定はないのですが、未経験のTestFlightに登録だけしてみました。内部テスターとして自分を登録し、iOSのTestFlightアプリからインストールしました。非公開のまま知り合いだけ内部テスターとして登録し、配布しても面白そうです。内部テスターは100人、外部テスター(アプリ要審査承認)は10000人まで登録できます。

Xcodeベータ版のVision Proシミュレータでも使ってみました。Vision ProではiPad版UIで使用できます。

import Foundation

struct ThreadContent{
    let threadNum: Int
    let threadTitle: String
    let threadPostNum: Int
}

func splitTitle(threadString: String) async -> ThreadContent {
    var threadContent: ThreadContent
    var colonIndex: String.Index?
    var openParenIndex: String.Index?
    var number: Int = 0
    var title: String = ""
    var postCount: Int = 0
    
    // 番号を取り出す(最初のコロンの左側)
    if let index = threadString.firstIndex(of: ":") {
        colonIndex = index
        if let colonIndex = colonIndex {
            number = Int(threadString[..<colonIndex]) ?? 0
            print("番号: \(number)")
        }
    }
    
    if let index = threadString.lastIndex(of: "(") {
        // タイトルを取り出す(最初のコロンの右が始点、末尾から最初の開き括弧の左が終点)
        openParenIndex = index
        if let colonIndex = colonIndex, let openParenIndex = openParenIndex {
            title = String(threadString[threadString.index(after: colonIndex)..<openParenIndex])
            print("タイトル: \(title)")
        }
        
        // 投稿数を取り出す(開き括弧右から末尾1文字を削除した数字)
        if let openParenIndex = openParenIndex {
            let postCountString = threadString[threadString.index(after: openParenIndex)..<threadString.index(before: threadString.endIndex)]
            postCount = Int(postCountString) ?? 0
            print("投稿数: \(postCount)")
        }

    }
    
    threadContent = ThreadContent(threadNum: number, threadTitle: title, threadPostNum: postCount)
    
    return threadContent
}
TestFlightにアルファ版を登録、配布可能にした
Vision Proシミュレータでテスト

[Swift] 50 BBS閲覧アプリiOS版製作 その4 スレッド一覧表示 / kanna

[Mac M2 Pro 12CPU, Ventura 13.6, iOS 17.0.3, Xcode 15.0]

選択した掲示板のスレッド一覧を表示させました。

掲示板のHTMLをパースして要素を取り出し、ChatGPTでC++の関数をSwiftへ変換して動作させました。FLTKに相当するViewについては自分で叩き台を考える必要があります。

Swiftの文法が分かっていればもっとスムーズに書けるはずですが、まあこれから学んでいきます。

次は選択スレッドの表示およびdatファイルの保存に関する実装に取り組みます。

プロジェクトの構成
import Foundation
import Kanna

struct Thread : Identifiable {
    let id = UUID()
    let threadID: String
    let threadTitle: String
    let threadNum: Int
}

func getThreads(url: String) async -> [Thread] { // urlはboard.url
    var threads: [Thread] = []
    var threadNumDict: [String: Int] = [:]
    var threadNum = 0
    
    let boardURL = url + "subback.html"
    guard let url = URL(string: boardURL) else { return [] }

    do {
        let htmlString = try String(contentsOf: url, encoding: .shiftJIS)
        guard let doc = try? HTML(html: htmlString, encoding: .shiftJIS) else { return [] }

        for link in doc.xpath("//*[name()='small']/*[name()='a']") {
            guard let id = link["href"], let title = link.text else { continue }
            
            print(id)
            
            let truncatedID = String(id.dropLast(4))
                
            let idRegex = try! NSRegularExpression(pattern: "^[0-9]+$")
            let idRange = NSRange(location: 0, length: truncatedID.utf16.count)
            let isNumericID = idRegex.firstMatch(in: truncatedID, options: [], range: idRange) != nil
            
            if !isNumericID {
                continue
            }

            if threadNumDict[truncatedID] == nil {
                threadNum += 1
                threadNumDict[truncatedID] = threadNum
            }

            threads.append(Thread(threadID: truncatedID, threadTitle: title, threadNum: threadNumDict[truncatedID]!))
            
            
        }
    } catch {
        print("Error: \(error)")
    }

    return threads
}
import SwiftUI

struct BoardView: View {
    @State private var boardCategories: [BoardCategory] = []

    var body: some View {
        NavigationView {
            VStack{
                Text("板一覧")
                List(boardCategories) { boardCategory in
                    DisclosureGroup(boardCategory.category) {
                        ForEach(boardCategory.boards) { board in
                            NavigationLink(destination: ThreadView(board: board)) {
                                ScrollView {
                                    Text(board.name)
                                }
                            }
                        }
                    }
                }
            }
            Spacer()
        }
        .padding()
        .task {
            boardCategories = await getBoards()
        }
    }
}

struct BoardView_Previews: PreviewProvider {
    static var previews: some View {
        BoardView()
    }
}
import SwiftUI

struct ThreadView: View {
    @State private var threads: [Thread] = []
    var board: Board?

    var body: some View {
        VStack {
            Text("スレッド一覧")
            List{
                ForEach(threads) { thread in
                    Text(thread.threadTitle)
                }
            }
            Spacer()
        }
        .padding()
        .task {
            if let board = board {
               threads = await getThreads(url: board.url)
            }
        }
    }
}

struct ThreadView_Previews: PreviewProvider {
    static var previews: some View {
        ThreadView()
    }
}

[Swift] 49 BBS閲覧アプリiOS版製作 その3 掲示板一覧表示 / kanna(libxml2)でスクレイピング

[Mac M2 Pro 12CPU, Ventura 13.6, iOS 17.0.3, Xcode 15.0]

BBS閲覧アプリiOS版の初期画面として掲示板一覧をツリー表示させました。カテゴリーのリストを登場順に並べるのに少し手間取りました。

ChatGPT(gpt-4)をうまく誘導しながら完成させました。構造体で解決しなくて安易にクラスにしようとするなど一々大げさな反応をするので、こちらで手綱を引かないと無駄に大掛かりなコードになります。しかもどんどん的外れな方向に進んでいくので要注意です。

今日ここまでのgpt-4利用料は330円($2.19)ほど。新しい言語なのでgpt-3.5はなるべく避けたいところですが、やはりgpt-4はここぞという時に使うようにします。

import Foundation
import Kanna

struct Board : Identifiable {
    let id = UUID()
    let url: String
    let name: String
    let category: String
    let categoryNum: Int
}

struct BoardCategory : Identifiable {
    let id = UUID()
    let category: String
    let boards: [Board]
    let categoryNum: Int
}


func getBoards() async -> [BoardCategory] {
    var boards: [Board] = []
    var categoryNumDict: [String: Int] = [:]
    var categoryNum = 0
    guard let url = URL(string: "掲示板一覧のURL") else { return [] }

    do {
        let htmlString = try String(contentsOf: url, encoding: .shiftJIS)
        guard let doc = try? HTML(html: htmlString, encoding: .shiftJIS) else { return [] }

        for link in doc.xpath("//*[name()='a']") {
            guard let url = link["href"], let name = link.text else { continue }

            var category = "XXX" // カテゴリー名の初期値
            var prevElement = link.previousSibling
            while prevElement != nil && prevElement?.tagName != "b" {
                prevElement = prevElement?.previousSibling
            }
            if let prevElement = prevElement {
                category = prevElement.text ?? "XXX"
            }

            if categoryNumDict[category] == nil {
                categoryNum += 1
                categoryNumDict[category] = categoryNum
            }

            boards.append(Board(url: url, name: name, category: category, categoryNum: categoryNumDict[category]!))
        }
    } catch {
        print("Error: \(error)")
    }
    
    let groupedBoards = Dictionary(grouping: boards, by: { $0.category })
    
    var boardCategories: [BoardCategory] = groupedBoards.map { BoardCategory(category: $0.key, boards: $0.value, categoryNum: categoryNumDict[$0.key]!) }
    boardCategories.sort { $0.categoryNum < $1.categoryNum }

    return boardCategories
}

[Swift] 48 BBS閲覧アプリiOS版製作 その2 Swift Package Manager / Kanna

[Mac M2 Pro 12CPU, Ventura 13.6, iOS 17.0.3, Xcode 15.0]

BBS閲覧アプリiOS版のプロジェクトにHTMLパーサー Kannaを導入しました。パッケージ管理システム Swift Package Managerを使いました。

CocoaPodsとは打って変わっての使いやすさでした。さすが純正は違います。2019年10月リリースのXcode 11からコマンドラインだけでなくIDEで使用可能になったようです。

[File] – [Add Package Dependencies]から右上窓にGitHubのURLを入力する