Remove classifier version from database

This commit is contained in:
Christoph Hagen 2023-04-17 14:20:13 +02:00
parent 0e2b0b42ff
commit 344d926b9b
4 changed files with 85 additions and 28 deletions

View File

@ -357,6 +357,7 @@ struct ContentView: View {
private func downloadClassifier() {
Task {
await database.downloadClassifier()
await database.downloadClassifierClasses()
}
}

View File

@ -17,9 +17,6 @@ struct Cap {
/// The index of the main image for the cap
var mainImage: Int
/// The version of the first classifier capable of recognizing the cap
var classifierVersion: Int?
var color: Color?
/// The subpath to the main image on the server
@ -44,13 +41,12 @@ struct Cap {
- Parameter id: The unique id of the cap
- Parameter name: The name associated with the cap
*/
init(id: Int, name: String, classifier: Int? = nil) {
init(id: Int, name: String) {
self.id = id
self.name = name
self.cleanName = name.clean
self.imageCount = 0
self.mainImage = 0
self.classifierVersion = classifier
}
init(data: CapData) {
@ -59,7 +55,6 @@ struct Cap {
self.cleanName = data.name.clean
self.imageCount = data.count
self.mainImage = data.mainImage
self.classifierVersion = data.classifierVersion
}
var data: CapData {
@ -67,7 +62,6 @@ struct Cap {
name: name,
count: imageCount,
mainImage: mainImage,
classifierVersion: classifierVersion,
color: color)
}
@ -76,30 +70,18 @@ struct Cap {
self.cleanName = data.name.clean
self.imageCount = data.count
self.mainImage = data.mainImage
self.classifierVersion = data.classifierVersion
}
static func ==(lhs: Cap, rhs: CapData) -> Bool {
lhs.id == rhs.id &&
lhs.name == rhs.name &&
lhs.imageCount == rhs.count &&
lhs.mainImage == rhs.mainImage &&
lhs.classifierVersion == rhs.classifierVersion
lhs.mainImage == rhs.mainImage
}
static func !=(lhs: Cap, rhs: CapData) -> Bool {
!(lhs == rhs)
}
func classifiable(by classifierVersion: Int?) -> Bool {
guard let version = classifierVersion else {
return false
}
guard let own = self.classifierVersion else {
return false
}
return version >= own
}
}
extension Cap {
@ -130,7 +112,6 @@ extension Cap: Codable {
case cleanName = "c"
case imageCount = "i"
case mainImage = "m"
case classifierVersion = "v"
case color = "f"
}
}

View File

@ -10,8 +10,6 @@ struct CapData: Codable {
var mainImage: Int
var classifierVersion: Int?
var color: Cap.Color?
enum CodingKeys: String, CodingKey {
@ -19,7 +17,6 @@ struct CapData: Codable {
case name = "n"
case count = "c"
case mainImage = "m"
case classifierVersion = "v"
case color = "f"
}
}

View File

@ -106,6 +106,35 @@ final class Database: ObservableObject {
@Published
var isUploading = false
@AppStorage("classifierClasses")
private var _classifierClassesString: String = ""
private var _classifierClassesCache: Set<Int>?
private var classifierClasses: Set<Int> {
get {
_classifierClassesCache ?? loadClassifierClasses()
}
set {
_classifierClassesCache = newValue
DispatchQueue.main.async {
self._classifierClassesString = newValue.map { "\($0)" }.joined(separator: ",")
}
}
}
private func loadClassifierClasses() -> Set<Int> {
let elements: [Int] = _classifierClassesString.components(separatedBy: ",").compactMap {
guard let id = Int($0) else {
log("Failed to load classifier class from '\($0)'")
return nil
}
return id
}
_classifierClassesCache = Set(elements)
return _classifierClassesCache!
}
init(server: URL, folder: URL = FileManager.default.documentDirectory) {
self.serverUrl = server
@ -151,6 +180,10 @@ final class Database: ObservableObject {
private var serverClassifierUrl: URL {
serverUrl.appendingPathComponent("classifier.mlmodel")
}
private var serverClassifierClassesUrl: URL {
serverUrl.appendingPathComponent("classifier.classes")
}
private var serverClassifierVersionUrl: URL {
serverUrl.appendingPathComponent("version")
@ -359,6 +392,48 @@ final class Database: ObservableObject {
log("Downloaded classifier \(classifierVersion)")
return true
}
@discardableResult
func downloadClassifierClasses() async -> Bool {
log("Downloading classifier classes")
let data: Data
let response: URLResponse
do {
(data, response) = try await URLSession.shared.data(from: serverClassifierClassesUrl)
} catch {
log("Failed to download classifier classes: \(error)")
return false
}
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
return false
}
guard let string = String(data: data, encoding: .utf8) else {
log("Classifier classes is invalid data (not a string)")
return false
}
let classes = string.components(separatedBy: ",")
// Validate input
var isValid = true
let ids: [Int] = classes.compactMap { s in
guard let id = Int(s) else {
log("Invalid id '\(s)' in downloaded classes list")
isValid = false
return nil
}
if caps[id] == nil {
// Caps which are deleted may still be recognized
return nil
}
return id
}
guard isValid else {
return false
}
self.classifierClasses = Set(ids)
return true
}
/**
Indicate that the cap has pending operations, such as determining the color or a thumbnail
@ -374,7 +449,7 @@ final class Database: ObservableObject {
// MARK: Adding new data
func save(newCap name: String) -> Cap {
let cap = Cap(id: nextCapId, name: name, classifier: nil)
let cap = Cap(id: nextCapId, name: name)
caps[cap.id] = cap
DispatchQueue.main.async {
self.changedCaps.insert(cap.id)
@ -745,6 +820,10 @@ final class Database: ObservableObject {
}
}
}
func canClassify(cap id: Int) -> Bool {
classifierClasses.contains(id)
}
private func getClassifier() -> Classifier? {
if let classifier = classifier {
@ -847,8 +926,7 @@ final class Database: ObservableObject {
}
var classifierClassCount: Int {
let version = classifierVersion
return caps.values.filter { $0.classifiable(by: version) }.count
classifierClasses.count
}
func imageCacheSize() async -> Int {
@ -883,7 +961,7 @@ extension Database {
static var largeMock: Database {
let db = Database(server: URL(string: "https://christophhagen.de/caps")!)
db.caps = (1..<500)
.map { Cap(id: $0, name: "Cap \($0)", classifier: nil)}
.map { Cap(id: $0, name: "Cap \($0)") }
.reduce(into: [:]) { $0[$1.id] = $1 }
db.image = UIImage(systemSymbol: .photo)
return db