頭と尻尾はくれてやる!

iOSアプリなどの開発日記です


Create MLのObject Detection用のデータセットを作成する

Create ML起動時の画面

↑これはCreate MLのプロジェクト作成時画面だけど、このObject Detectionで生成されたNNは、画像内にどんなオブジェクトがどこにあるかを出力してくれる。
このデータセット(画像とJSONファイル)をmacOSアプリで作成しようとしたんだが、はまったことが二つ。

1つ目)
JSONファイルに画像ファイル名やオブジェクトのラベルや座標を持たせる必要があるんだが、この座標はx, y, width, heightというキーで整数を与える。
このx, yは画像の中心であり、左上でも左下でもない!

2つ目)
macOSでNSViewなどを扱ったことがあればご存じだろうが、、、macOSではiOSと違い、原点が左下だ。うっかり左下を原点とした座標を与えていたのだが、JSONファイルで与えるべき座標は左上が原点としての値だ。

とまあしょうもないところで随分と時間をロスしたのでこれからやる場合にはご注意を。

なお、Create MLで学習させて無事mlmodelファイルを作成し、それをiOSアプリに持ってきて使うと出力される座標は左下が原点になってる。なんでやねーん!
スポンサーサイト






SwiftUIのTextFieldで指定の桁数で浮動小数点数を入力する

アプリでユーザーがあるFloatの値を設定できるようなSwiftUIのTextFieldを置きたい。
何桁入力されても意味ないので小数点以下の桁数を例えば2にしたい。

ようやく辿り着いた記述がこんな感じ。
TextField("",  value: $hoge, format: .number.precision(.fractionLength(2)))
.keyboardType(.decimalPad)
.textFieldStyle(RoundedBorderTextFieldStyle())
TextFieldだとformatter:ってコードがよく見つかるけどformat:ってのもあるんだな。
keyboardTypeをdecimalPadにすれば小数点の入力もできるキーボードが表示される。

入力時は0.12345とか可能ではあるが、TextFieldからフォーカスが外れるなどすると指定の桁数に丸めて表示される。かつ、実際の値を確認しても丸められてる。


↑動作時の様子。

なおキーボードを閉じるのは、ZStackで1番奥のViewに以下のような感じでジェスチャーを設定してresignFirstResponderしてる。
.onTapGesture {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}




マイクラのmodでScreenにスライダーを置く

Minecraftのmod(forge 1.17.1-37.1.1)でスライダーを使いたいのでvanillaのコードを参考に試してみた。
Buttonみたいにさくっと使えるとラクだけど機能が複雑なのかvanillaでは
AbstractSliderButton
を継承したクラスで処理を記述してた。

↑動作の様子はこんな感じ。
コードは以下のような感じで。CycleButtonの時と同じでScreenのサブクラス内に記述してる。
   public float mySliderValue = 0F;
private void createView() {
this.addRenderableWidget(new MySlider(x, y, w, h, mySliderValue, minValue, maxValue));
}

@OnlyIn(Dist.CLIENT)
class MySlider extends AbstractSliderButton {
private final double minValue;
private final double maxValue;

public MySlider(int x, int y, int w, int h, float initValue, float minValue, float maxValue) {
super(x, y, w, h, TextComponent.EMPTY, 0.0D);
// super(x, y, w, h, new TextComponent("text component"), 0.0D);//(A)
this.minValue = (double)minValue;
this.maxValue = (double)maxValue;
this.value = (double)((Mth.clamp(initValue, minValue, maxValue) - minValue) / (maxValue - minValue));
this.updateMessage();
}

public void applyValue() {
MyScreen.this.mySliderValue = (float)Mth.lerp(Mth.clamp(this.value, 0.0D, 1.0D), this.minValue, this.maxValue);
}

protected void updateMessage() {
System.out.println("updateMessage");
}

public void onClick(double p_89954_, double p_89955_) {
System.out.println("onClick");
}

public void onRelease(double p_89957_, double p_89958_) {
System.out.println("onRelease");
}
}
スライダーの上に数字を表示させてるが、MyScreen (Screenのサブクラス)内のrender内でdrawStringしてるだけで表示を更新してくれる。

(A)のところでsuperに渡す引数のComponentにTextComponentのオブジェクトを渡すと、

TextComponentを表示させたSlider

↑こんな感じで文字を表示する。この使い方はあまり実用性はないかもしれないが。


マイクラのCycleButton.builderの最小実装

Minecraftのmod(forge 1.17.1-37.1.1)でCycleButtonがようやく動いた。
CycleButton.onOffBuilder
CycleButton.booleanBuilder
↑この2つは取りうる値が2種類しかなくすんなりいけたのだが、選択肢が3つ以上ある場合にハマった。
もっともJavaに慣れてる人なら簡単だったのかもしれないけど。
Screenのサブクラス内のコードは以下のような感じで。
    private MyMode myMode = MyMode.MODE1;

private void createView() {
this.addRenderableWidget(CycleButton.builder(MyMode::getCurrentComponent)
.withValues(MyMode.values())
.withInitialValue(myMode)
.create(x, y, width, height, new TextComponent("Mode"), (p_169299_, p_169300_) -> {
this.myMode = p_169300_;
}));
}

public static enum MyMode implements StringRepresentable {
MODE1("mode 1"),
MODE2("mode 2"),
MODE3("mode 3");

private final String name;
private MyMode(String p_59455_) {
this.name = p_59455_;
}

@Override
public String getSerializedName() {
return this.name;
}

public TextComponent getCurrentComponent() {
return new TextComponent(this.name);
}
}

CycleButton
↑スクショ。クリックするたびに表示内容が変わります(これは画像なので変わらないけど)。
なお、1.18だとどうなのかはわかりません。1.18にアップデートしたら挙動がおかしくなり結局1.17に戻していじっています、、、orz


PHPickerViewControllerで動画ファイルを選択した後、Task内だと動かない

現在開発してるiOSアプリで、iPhoneのライブラリから動画ファイルを選択する処理を作ってた。
最近PHPickerControllerというのがあるみたいで試してたんだが、ユーザーに許可を得なくてもいいらしく、こりゃいいや!と試してたんだが、、、
PHPickerViewControllerDelegateのメソッドである
func picker(PHPickerViewController, didFinishPicking: [PHPickerResult])
でurl: URLを取得まではOK。その後続けて処理をしていくんだが、例えば動画が記録された日付を得るとする。

//上記メソッド内
let asset = AVURLAsset(url: url)
guard let creationDate = asset.creationDate?.value as? Date else { abort() }
print(creationDate)

↑これは意図通りに動く。
問題はasync/awaitを使うために以下のように書き換えた場合だ。

Task {
let asset = AVURLAsset(url: url)
guard let creationDate = asset.creationDate?.value as? Date else { abort() }
print(creationDate)
}

↑これだとabortで処理が止まる。urlは有効で、assetは一応できてるっぽい。
Task内の処理にtry await someFunction(…とかあるんだがそこまで辿り着かない。orz

困ったなあ、、、
試しにUIImagePickerController(PHPickerControllerが出る前はこれを使ってたよね)でやってみるとこれが動いたんだわ。
でもUIImagePickerControllerのUIってあまり好きじゃないしなあ。
ユーザーに許可を求める必要あるし(できればない方がいい)。
せっかく苦労してasync/await実装できたのに、元に戻してcompletionHandler使うか?うーん、、、

と思ってたところ試しに得たファイルをdocumentDirectoryにコピーしてから、それに対して処理をするようにしたらasync/await使ってても動いた!

めでたし、めでたし。

おおよそのコードはこんな感じ。
    @IBAction func tappedSelectVideoButton() {

var configuration = PHPickerConfiguration()
configuration.selectionLimit = 1//default
configuration.filter = .videos
configuration.preferredAssetRepresentationMode = .current

let pickerViewController = PHPickerViewController(configuration: configuration)
pickerViewController.delegate = self
self.present(pickerViewController, animated: true)
}

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {

picker.dismiss(animated: true)

guard let provider = results.first?.itemProvider else { return }
guard let typeIdentifier = provider.registeredTypeIdentifiers.first else { return }

if provider.hasItemConformingToTypeIdentifier(typeIdentifier) {
provider.loadFileRepresentation(forTypeIdentifier: typeIdentifier) { [weak self] (url, error) in
if let url = url {
let copiedFileURL = self?.copyFile(url)//詳細略
assert(copiedFileURL != nil, "copiedFileURL is nil")

Task {
do {
let r = try await analyzer.start(copiedFileURL!)
print(r)
} catch {
//以下略






Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.