Separate classes to file
This commit is contained in:
parent
289e927c6a
commit
3ed5f0e0ff
@ -10,9 +10,6 @@ struct Cap: Codable {
|
|||||||
|
|
||||||
var mainImage: Int
|
var mainImage: Int
|
||||||
|
|
||||||
/// The version of the first classifier trained on this cap
|
|
||||||
var classifierVersion: Int?
|
|
||||||
|
|
||||||
var color: Color?
|
var color: Color?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
@ -20,7 +17,6 @@ struct Cap: 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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ extension CapServer {
|
|||||||
try authenticator.authorize(request)
|
try authenticator.authorize(request)
|
||||||
let body = try request.getStringBody(request: "/classes/:date")
|
let body = try request.getStringBody(request: "/classes/:date")
|
||||||
|
|
||||||
self.updateTrainedClasses(content: body)
|
try self.saveTrainedClasses(content: body)
|
||||||
self.removeAllEntriesInImageChangeList(before: date)
|
self.removeAllEntriesInImageChangeList(before: date)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ final class CapServer {
|
|||||||
|
|
||||||
private let classifierFile: URL
|
private let classifierFile: URL
|
||||||
|
|
||||||
|
private let classifierClassesFile: URL
|
||||||
|
|
||||||
private let changedImagesFile: URL
|
private let changedImagesFile: URL
|
||||||
|
|
||||||
private let fm = FileManager.default
|
private let fm = FileManager.default
|
||||||
@ -87,6 +89,7 @@ final class CapServer {
|
|||||||
self.htmlFile = folder.appendingPathComponent("count.html")
|
self.htmlFile = folder.appendingPathComponent("count.html")
|
||||||
self.classifierVersionFile = folder.appendingPathComponent("classifier.version")
|
self.classifierVersionFile = folder.appendingPathComponent("classifier.version")
|
||||||
self.classifierFile = folder.appendingPathComponent("classifier.mlmodel")
|
self.classifierFile = folder.appendingPathComponent("classifier.mlmodel")
|
||||||
|
self.classifierClassesFile = folder.appendingPathComponent("classifier.classes")
|
||||||
self.changedImagesFile = folder.appendingPathComponent("changes.txt")
|
self.changedImagesFile = folder.appendingPathComponent("changes.txt")
|
||||||
self.changedImageEntryDateFormatter = DateFormatter()
|
self.changedImageEntryDateFormatter = DateFormatter()
|
||||||
changedImageEntryDateFormatter.dateFormat = "yy-MM-dd-HH-mm-ss"
|
changedImageEntryDateFormatter.dateFormat = "yy-MM-dd-HH-mm-ss"
|
||||||
@ -465,7 +468,6 @@ final class CapServer {
|
|||||||
}
|
}
|
||||||
var cap = cap
|
var cap = cap
|
||||||
cap.count = 0
|
cap.count = 0
|
||||||
cap.classifierVersion = nil
|
|
||||||
caps[cap.id] = cap
|
caps[cap.id] = cap
|
||||||
saveCapCountHTML()
|
saveCapCountHTML()
|
||||||
updateGridCapCount()
|
updateGridCapCount()
|
||||||
@ -537,18 +539,33 @@ final class CapServer {
|
|||||||
|
|
||||||
// MARK: Classifier
|
// MARK: Classifier
|
||||||
|
|
||||||
func updateTrainedClasses(content: String) {
|
func saveTrainedClasses(content: String) throws {
|
||||||
let trainedCaps = content
|
let classes = content.components(separatedBy: ",")
|
||||||
.components(separatedBy: "\n")
|
|
||||||
.compactMap(Int.init)
|
// Validate input
|
||||||
let version = classifierVersion
|
try classes.forEach { s in
|
||||||
for cap in trainedCaps {
|
guard let id = Int(s) else {
|
||||||
// Set current classifier
|
log("Invalid id '\(s)' in uploaded id list")
|
||||||
if caps[cap]?.classifierVersion == nil {
|
throw Abort(.badRequest)
|
||||||
caps[cap]?.classifierVersion = version
|
}
|
||||||
|
guard caps[id] != nil else {
|
||||||
|
log("Unknown id '\(id)' in uploaded id list")
|
||||||
|
throw Abort(.badRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log("Updated \(trainedCaps.count) classifier classes")
|
|
||||||
|
guard let data = content.data(using: .utf8) else {
|
||||||
|
log("Failed to get classes data for writing")
|
||||||
|
throw Abort(.internalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try data.write(to: classifierClassesFile)
|
||||||
|
log("Updated \(classes.count) classifier classes")
|
||||||
|
} catch {
|
||||||
|
log("Failed to write classifier classes: \(error)")
|
||||||
|
throw Abort(.internalServerError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func save(classifier: Data, version: Int) throws {
|
func save(classifier: Data, version: Int) throws {
|
||||||
|
@ -156,6 +156,12 @@ final class ClassifierCreator {
|
|||||||
}
|
}
|
||||||
let changedMainImages = changedImageList.filter { $0.image == 0 }.map { $0.cap }
|
let changedMainImages = changedImageList.filter { $0.image == 0 }.map { $0.cap }
|
||||||
let classes = imageCounts.keys.sorted()
|
let classes = imageCounts.keys.sorted()
|
||||||
|
|
||||||
|
// Delete any image folders not present as caps
|
||||||
|
guard deleteUnnecessaryImageFolders(caps: classes) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return (classes, missingImageList.count + changedImageList.count, changedMainImages)
|
return (classes, missingImageList.count + changedImageList.count, changedMainImages)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +182,33 @@ final class ClassifierCreator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func deleteUnnecessaryImageFolders(caps: [Int]) -> Bool {
|
||||||
|
let validNames = caps.map { String(format: "%04d", $0) }
|
||||||
|
let folders: [String]
|
||||||
|
do {
|
||||||
|
folders = try FileManager.default.contentsOfDirectory(atPath: imageDirectory.path)
|
||||||
|
} catch {
|
||||||
|
print("[ERROR] Failed to get list of image folders: \(error)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for folder in folders {
|
||||||
|
if validNames.contains(folder) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a valid cap folder
|
||||||
|
let url = imageDirectory.appendingPathComponent(folder)
|
||||||
|
do {
|
||||||
|
try FileManager.default.removeItem(at: url)
|
||||||
|
print("[ERROR] Removed unused image folder '\(folder)'")
|
||||||
|
} catch {
|
||||||
|
print("[ERROR] Failed to delete unused image folder \(folder): \(error)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private func imageUrl(base: URL, cap: Int, image: Int) -> URL {
|
private func imageUrl(base: URL, cap: Int, image: Int) -> URL {
|
||||||
base.appendingPathComponent(String(format: "%04d/%04d-%02d.jpg", cap, cap, image))
|
base.appendingPathComponent(String(format: "%04d/%04d-%02d.jpg", cap, cap, image))
|
||||||
}
|
}
|
||||||
@ -329,7 +362,7 @@ final class ClassifierCreator {
|
|||||||
let dateString = df.string(from: lastUpdate)
|
let dateString = df.string(from: lastUpdate)
|
||||||
return await post(
|
return await post(
|
||||||
url: server.appendingPathComponent("classes/\(dateString)"),
|
url: server.appendingPathComponent("classes/\(dateString)"),
|
||||||
body: classes.map(String.init).joined(separator: "\n").data(using: .utf8)!)
|
body: classes.map(String.init).joined(separator: ",").data(using: .utf8)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Step 7: Create thumbnails
|
// MARK: Step 7: Create thumbnails
|
||||||
|
Loading…
Reference in New Issue
Block a user