annotations CLI and file UX rework (#1049)

* rename config param label-file

* annotations rework - CLI params, file naming and backups

* lint

* improve cli option error checks

* enable session cookies

* enable session cookies

* add session id

* name annotations file in multi-dataset and multi-user safe manner

* pass data user hash to front-end

* add annotation collection name support to front-end

* add constant for annotation data collection name

* parameterize annotation collection name; make it sticky in the session

* clarify comments

* hard wire a temporary data collection name for testing

* prettier

* test comment

* package command

* set annotations  filename dialog

* name  and hash are visible

* wire up data collection capture
This commit is contained in:
Bruce Martin
2019-11-25 15:28:28 -08:00
committed by GitHub
parent 575f71aaee
commit a593e95ab3
18 changed files with 551 additions and 130 deletions
+40 -31
View File
@@ -1,52 +1,61 @@
"""
Helpers for user annotations / label_file parameter
Helpers for user annotations
"""
from os.path import exists, splitext, getsize
from os import remove, rename
import os
import os.path
from datetime import datetime
import pandas as pd
def read_labels(fname):
if exists(fname) and getsize(fname) > 0:
if fname is not None and os.path.exists(fname) and os.path.getsize(fname) > 0:
return pd.read_csv(fname, dtype='category', index_col=0, header=0, comment='#')
else:
return pd.DataFrame()
def write_labels(fname, df, header=None):
rotate_fname(fname)
def write_labels(fname, df, header=None, backup_dir=None):
if backup_dir is not None:
backup(fname, backup_dir)
# rotate_fname(fname, backup_dir)
if not df.empty:
f = open(fname, 'a', newline="")
if header is not None:
f.write(header)
df.to_csv(f)
with open(fname, 'w', newline="") as f:
if header is not None:
f.write(header)
df.to_csv(f)
else:
open(fname, 'a').close()
open(fname, 'w').close()
def rotate_fname(fname):
def backup(fname, backup_dir, max_backups=9):
"""
save N backups of file.
fname -> fname-0
fname-0 -> fname->1
...
fname-(N-1) -> fname-N
save N backups of file to backup_dir.
1. fname -> backup_dir/fname-TIME
2. delete excess files in backup_dir
"""
def rotate(src, dst):
if exists(src):
if exists(dst):
remove(dst)
rename(src, dst)
# Make sure there is work to do
if not os.path.exists(fname):
return
rotation_size = 9 # rotation size
name, ext = splitext(fname)
# Ensure backup_dir exists
if not os.path.exists(backup_dir):
os.mkdir(backup_dir)
# rotate existing files
for i in range(rotation_size - 1, 0, -1):
src = f"{name}-{i}{ext}"
tgt = f"{name}-{i+1}{ext}"
rotate(src, tgt)
# Save current file to backup_dir
fname_base = os.path.basename(fname)
fname_base_root, fname_base_ext = os.path.splitext(fname_base)
# don't use ISO standard time format, as it contains characters illegal on some filesytems.
nowish = datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
backup_fname = os.path.join(backup_dir, f"{fname_base_root}-{nowish}{fname_base_ext}")
if os.path.exists(backup_fname):
os.remove(backup_fname)
os.rename(fname, backup_fname)
tgt = f"{name}-1{ext}"
rotate(fname, tgt)
# prune the backup_dir to max number of backup files, keeping the most recent backups
backups = list(filter(lambda s: s.startswith(fname_base_root), os.listdir(backup_dir)))
excess_count = len(backups) - max_backups
if excess_count > 0:
backups.sort()
for bu in backups[0:excess_count]:
os.remove(os.path.join(backup_dir, bu))