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() { private func downloadClassifier() {
Task { Task {
await database.downloadClassifier() await database.downloadClassifier()
await database.downloadClassifierClasses()
} }
} }

View File

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

View File

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

View File

@ -107,6 +107,35 @@ final class Database: ObservableObject {
@Published @Published
var isUploading = false 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) { init(server: URL, folder: URL = FileManager.default.documentDirectory) {
self.serverUrl = server self.serverUrl = server
self.folderUrl = folder self.folderUrl = folder
@ -152,6 +181,10 @@ final class Database: ObservableObject {
serverUrl.appendingPathComponent("classifier.mlmodel") serverUrl.appendingPathComponent("classifier.mlmodel")
} }
private var serverClassifierClassesUrl: URL {
serverUrl.appendingPathComponent("classifier.classes")
}
private var serverClassifierVersionUrl: URL { private var serverClassifierVersionUrl: URL {
serverUrl.appendingPathComponent("version") serverUrl.appendingPathComponent("version")
} }
@ -360,6 +393,48 @@ final class Database: ObservableObject {
return true 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 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 // MARK: Adding new data
func save(newCap name: String) -> Cap { 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 caps[cap.id] = cap
DispatchQueue.main.async { DispatchQueue.main.async {
self.changedCaps.insert(cap.id) self.changedCaps.insert(cap.id)
@ -746,6 +821,10 @@ final class Database: ObservableObject {
} }
} }
func canClassify(cap id: Int) -> Bool {
classifierClasses.contains(id)
}
private func getClassifier() -> Classifier? { private func getClassifier() -> Classifier? {
if let classifier = classifier { if let classifier = classifier {
return classifier return classifier
@ -847,8 +926,7 @@ final class Database: ObservableObject {
} }
var classifierClassCount: Int { var classifierClassCount: Int {
let version = classifierVersion classifierClasses.count
return caps.values.filter { $0.classifiable(by: version) }.count
} }
func imageCacheSize() async -> Int { func imageCacheSize() async -> Int {
@ -883,7 +961,7 @@ extension Database {
static var largeMock: Database { static var largeMock: Database {
let db = Database(server: URL(string: "https://christophhagen.de/caps")!) let db = Database(server: URL(string: "https://christophhagen.de/caps")!)
db.caps = (1..<500) 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 } .reduce(into: [:]) { $0[$1.id] = $1 }
db.image = UIImage(systemSymbol: .photo) db.image = UIImage(systemSymbol: .photo)
return db return db