Skip to content

Commit 6e85d9f

Browse files
committed
added another level of directories
1 parent 970b81e commit 6e85d9f

File tree

15 files changed

+277
-159
lines changed

15 files changed

+277
-159
lines changed

api/api.py

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,31 @@ def create_app(script_info: Optional[ScriptInfo] = None, data_path: Text = "data
3333

3434
cell_image_cache: Dict[Tuple[Text, int], CellGrid[PIL.Image]] = {}
3535

36-
def get_workdir(project: Text, subdir: Text) -> Text:
37-
return os.path.join(app.config[DATA_PATH], project, subdir)
38-
39-
@api.route('/')
40-
def get_all_projects():
41-
project_names = [os.path.join(app.config[DATA_PATH], project_name)
42-
for project_name in os.listdir(app.config[DATA_PATH])]
43-
project_names = [pn for pn in project_names if os.path.isdir(pn)]
44-
project_names = [pn for pn in project_names if not pn.startswith('.')]
45-
project_names = [os.path.basename(pn) for pn in project_names]
36+
def get_workdir(bucket: Text, project: Text, subdir: Text) -> Text:
37+
return os.path.join(app.config[DATA_PATH], bucket, project, subdir)
38+
39+
@api.route("/")
40+
def get_all_status_folders():
41+
data_path = app.config[DATA_PATH]
42+
project_buckets = table_annotator.io.get_all_non_hidden_dirs(data_path,
43+
return_base_names=True)
44+
return {"bucketNames": sorted(project_buckets)}
45+
46+
@api.route('/<bucket>/')
47+
def get_all_projects(bucket: str):
48+
bucket_dir = os.path.join(app.config[DATA_PATH], bucket)
49+
if not os.path.isdir(bucket_dir):
50+
return make_response({"msg": "The state dir does not exist."}, 404)
51+
project_names = table_annotator.io.get_all_non_hidden_dirs(bucket_dir,
52+
return_base_names=True)
4653
return {"projectNames": sorted(project_names)}
4754

48-
@api.route('/<project>')
49-
def get_project(project: Text):
50-
project_path = os.path.join(app.config[DATA_PATH], project)
55+
@api.route('/<bucket>/<project>')
56+
def get_project(bucket: str, project: str):
57+
project_path = os.path.join(app.config[DATA_PATH], bucket, project)
5158
if not os.path.isdir(project_path):
5259
return make_response({"msg": "The project does not exist."}, 404)
53-
54-
work_dirs = [os.path.join(os.path.join(project_path, d))
55-
for d in os.listdir(project_path)]
56-
work_dirs = [d for d in work_dirs if os.path.isdir(d)]
57-
60+
work_dirs = table_annotator.io.get_all_non_hidden_dirs(project_path)
5861
work_dir_infos = []
5962

6063
for work_dir in work_dirs:
@@ -86,9 +89,9 @@ def get_project(project: Text):
8689
"workPackages": work_dir_infos
8790
}}
8891

89-
@api.route('/<project>/<subdir>/images')
90-
def list_images(project: Text, subdir: Text):
91-
workdir = get_workdir(project, subdir)
92+
@api.route('/<bucket>/<project>/<subdir>/images')
93+
def list_images(bucket: Text, project: Text, subdir: Text):
94+
workdir = get_workdir(bucket, project, subdir)
9295
if not os.path.isdir(workdir):
9396
return make_response(
9497
{"msg": "The workdir you tried to access does not exist."}, 404)
@@ -105,17 +108,17 @@ def list_images(project: Text, subdir: Text):
105108
)
106109
center = {"x": width // 2, "y": height // 2}
107110
images_with_metadata.append(
108-
{"src": f"{project}/{subdir}/image/{image_name}", "width": width,
111+
{"src": f"{bucket}/{project}/{subdir}/image/{image_name}", "width": width,
109112
"height": height, "center": center,
110113
"name": image_name, "docId": os.path.splitext(image_name)[0],
111114
"hasPreAnnotatedData": has_pre_annotated_data,
112115
"hasMatchingData": has_matching_data
113116
})
114117
return {"images": images_with_metadata}
115118

116-
@api.route('/<project>/<subdir>/image/invert/<image_name>', methods=["POST"])
117-
def invert_image(project: Text, subdir: Text, image_name: Text):
118-
workdir = get_workdir(project, subdir)
119+
@api.route('/<bucket>/<project>/<subdir>/image/invert/<image_name>', methods=["POST"])
120+
def invert_image(bucket: Text, project: Text, subdir: Text, image_name: Text):
121+
workdir = get_workdir(bucket, project, subdir)
119122
image_path = os.path.join(workdir, image_name)
120123
if not os.path.isfile(image_path):
121124
return make_response({"msg": "The image you tried to invert"
@@ -125,9 +128,9 @@ def invert_image(project: Text, subdir: Text, image_name: Text):
125128
table_annotator.io.write_image(image_path, image)
126129
return make_response({"msg": "Ok"}, 200)
127130

128-
@api.route('/<project>/<subdir>/image/rotate/<image_name>', methods=["POST"])
129-
def rotate_image(project: Text, subdir: Text, image_name: Text):
130-
workdir = get_workdir(project, subdir)
131+
@api.route('/<bucket>/<project>/<subdir>/image/rotate/<image_name>', methods=["POST"])
132+
def rotate_image(bucket: Text, project: Text, subdir: Text, image_name: Text):
133+
workdir = get_workdir(bucket, project, subdir)
131134
image_path = os.path.join(workdir, image_name)
132135
if not os.path.isfile(image_path):
133136
return make_response({"msg": "The image you tried to invert"
@@ -137,9 +140,9 @@ def rotate_image(project: Text, subdir: Text, image_name: Text):
137140
table_annotator.io.write_image(image_path, image)
138141
return make_response({"msg": "Ok"}, 200)
139142

140-
@api.route('/<project>/<subdir>/image/<image_name>')
141-
def get_image(project: Text, subdir: Text, image_name: Text):
142-
workdir = get_workdir(project, subdir)
143+
@api.route('/<bucket>/<project>/<subdir>/image/<image_name>')
144+
def get_image(bucket: Text, project: Text, subdir: Text, image_name: Text):
145+
workdir = get_workdir(bucket, project, subdir)
143146
return send_from_directory(workdir, image_name)
144147

145148
@api.route('/<project>/<subdir>/state/<image_name>', methods=["GET"])
@@ -153,9 +156,9 @@ def get_document_state(project: Text, subdir: Text, image_name: Text):
153156
state = table_annotator.io.read_state_for_image(image_path)
154157
return state.dict()
155158

156-
@api.route('/<project>/<subdir>/state/<image_name>', methods=["POST"])
157-
def set_document_state(project: Text, subdir: Text, image_name: Text):
158-
workdir = get_workdir(project, subdir)
159+
@api.route('/<bucket>/<project>/<subdir>/state/<image_name>', methods=["POST"])
160+
def set_document_state(bucket: Text, project: Text, subdir: Text, image_name: Text):
161+
workdir = get_workdir(bucket, project, subdir)
159162
image_path = os.path.join(workdir, image_name)
160163
if not os.path.isfile(image_path):
161164
return make_response({"msg": "The image for which you tried to save "
@@ -166,10 +169,10 @@ def set_document_state(project: Text, subdir: Text, image_name: Text):
166169

167170
return {"msg": "OK"}
168171

169-
@api.route('/<project>/<subdir>/tables/<image_name>', methods=["POST"])
170-
def store_tables(project: Text, subdir: Text, image_name: Text):
172+
@api.route('/<bucket>/<project>/<subdir>/tables/<image_name>', methods=["POST"])
173+
def store_tables(bucket: Text, project: Text, subdir: Text, image_name: Text):
171174
"""Stores the tables."""
172-
workdir = get_workdir(project, subdir)
175+
workdir = get_workdir(bucket, project, subdir)
173176
image_path = os.path.join(workdir, image_name)
174177
if not os.path.isfile(image_path):
175178
return make_response({"msg": "The image for which you tried to save "
@@ -180,9 +183,9 @@ def store_tables(project: Text, subdir: Text, image_name: Text):
180183

181184
return {"msg": "OK"}
182185

183-
@api.route('/<project>/<subdir>/tables/<image_name>', methods=["GET"])
184-
def get_tables(project: Text, subdir: Text, image_name: Text):
185-
workdir = get_workdir(project, subdir)
186+
@api.route('/<bucket>/<project>/<subdir>/tables/<image_name>', methods=["GET"])
187+
def get_tables(bucket, project: Text, subdir: Text, image_name: Text):
188+
workdir = get_workdir(bucket, project, subdir)
186189
image_path = os.path.join(workdir, image_name)
187190
if not os.path.isfile(image_path):
188191
return make_response({"msg": "The image for which you tried to retrieve "
@@ -196,11 +199,11 @@ def get_tables(project: Text, subdir: Text, image_name: Text):
196199
return tables_json
197200

198201
@api.route(
199-
'/<project>/<subdir>/<image_name>/predict_table_structure/<int:table_id>',
202+
'/<bucket>/<project>/<subdir>/<image_name>/predict_table_structure/<int:table_id>',
200203
methods=["GET"])
201-
def predict_table_structure(project: Text, subdir: Text, image_name: Text,
204+
def predict_table_structure(bucket: Text, project: Text, subdir: Text, image_name: Text,
202205
table_id: int):
203-
workdir = get_workdir(project, subdir)
206+
workdir = get_workdir(bucket, project, subdir)
204207
image_path = os.path.join(workdir, image_name)
205208
if not os.path.isfile(image_path):
206209
return make_response({"msg": "The image does not exist."}, 404)
@@ -221,11 +224,11 @@ def predict_table_structure(project: Text, subdir: Text, image_name: Text,
221224

222225
return {"rows": rows}
223226

224-
@api.route('/<project>/<subdir>/<image_name>/predict_table_contents/<int:table_id>',
227+
@api.route('/<bucket>/<project>/<subdir>/<image_name>/predict_table_contents/<int:table_id>',
225228
methods=["GET"])
226-
def predict_table_contents(project: Text, subdir: Text,
229+
def predict_table_contents(bucket: Text, project: Text, subdir: Text,
227230
image_name: Text, table_id: int):
228-
workdir = get_workdir(project, subdir)
231+
workdir = get_workdir(bucket, project, subdir)
229232
image_path = os.path.join(workdir, image_name)
230233

231234
if not os.path.isfile(image_path):
@@ -255,11 +258,11 @@ def predict_table_contents(project: Text, subdir: Text,
255258
updated_cells),
256259
"columnTypes": column_types}
257260

258-
@api.route('/<project>/<subdir>/<image_name>/match_table_contents/<int:table_id>',
261+
@api.route('/<bucket>/<project>/<subdir>/<image_name>/match_table_contents/<int:table_id>',
259262
methods=["GET"])
260-
def match_table_contents(project: Text, subdir: Text,
263+
def match_table_contents(bucket: Text, project: Text, subdir: Text,
261264
image_name: Text, table_id: int):
262-
workdir = get_workdir(project, subdir)
265+
workdir = get_workdir(bucket, project, subdir)
263266
image_path = os.path.join(workdir, image_name)
264267

265268
if not os.path.isfile(image_path):
@@ -285,11 +288,11 @@ def match_table_contents(project: Text, subdir: Text,
285288

286289

287290
@api.route(
288-
'/<project>/<subdir>/<image_name>/apply_pre_annotated_table_content/<int:table_id>',
291+
'/<bucket>/<project>/<subdir>/<image_name>/apply_pre_annotated_table_content/<int:table_id>',
289292
methods=["GET"])
290-
def apply_pre_annotated_table_content(project: Text, subdir: Text,
293+
def apply_pre_annotated_table_content(bucket: Text, project: Text, subdir: Text,
291294
image_name: Text, table_id: int):
292-
workdir = get_workdir(project, subdir)
295+
workdir = get_workdir(bucket, project, subdir)
293296
image_path = os.path.join(workdir, image_name)
294297

295298
if not os.path.isfile(image_path):
@@ -313,12 +316,12 @@ def apply_pre_annotated_table_content(project: Text, subdir: Text,
313316
lambda c: {k: v for k, v in c.dict().items() if v is not None},
314317
updated_cells)}
315318

316-
@api.route('/<project>/<subdir>/<image_name>/cell_image/<int:table_id>/<int:row>/'
319+
@api.route('/<bucket>/<project>/<subdir>/<image_name>/cell_image/<int:table_id>/<int:row>/'
317320
'<int:col>/<int:table_hash>',
318321
methods=["GET"])
319-
def get_cell_image(project: Text, subdir: Text, image_name: Text,
322+
def get_cell_image(bucket: Text, project: Text, subdir: Text, image_name: Text,
320323
table_id: int, row: int, col: int, table_hash: int):
321-
workdir = get_workdir(project, subdir)
324+
workdir = get_workdir(bucket, project, subdir)
322325
image_path = os.path.join(workdir, image_name)
323326
if (image_path, table_hash) not in cell_image_cache:
324327
if not os.path.isfile(image_path):

api/table_annotator/io.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,13 @@ def table_as_json(table: Table) -> Dict[Text, Any]:
9696
lambda c: {k: v for k, v in c.items() if v is not None}, table_json["cells"]
9797
)
9898
return table_json
99+
100+
101+
def get_all_non_hidden_dirs(path: str, return_base_names: bool = False) -> list[str]:
102+
dirs = [os.path.join(path, project_name)
103+
for project_name in os.listdir(path)]
104+
dirs = [d for d in dirs if os.path.isdir(d) and not d.startswith('.')]
105+
if return_base_names:
106+
return [os.path.basename(d) for d in dirs]
107+
else:
108+
return dirs

src/Annotator.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {v4 as uuidv4} from "uuid";
44
import { GlobalHotKeys } from "react-hotkeys";
55
import styled from 'styled-components'
66

7-
import {getDataDir, getProject} from './path';
7+
import {getDataDir, getProject, getProjectBucket} from './path';
88
import HelpScreen from "./components/HelpScreen";
99
import DocumentImage from "./components/DocumentImage";
1010
import {useStore} from "./store";
@@ -49,9 +49,10 @@ let rateToken = ""
4949
const pushTablesToApi = async(state: AnnotatorState, previousState: AnnotatorState) => {
5050
if(state.isFetchingTables) return
5151
if(state.tables === previousState.tables) return
52+
const bucket = getProjectBucket()
5253
const project = getProject()
5354
const dataDir = getDataDir()
54-
if (project === undefined || dataDir === undefined) return
55+
if (bucket === undefined || project === undefined || dataDir === undefined) return
5556
const {currentImageIndex, images, tables} = state
5657
if(currentImageIndex === undefined || images === undefined) return
5758
const image = images[currentImageIndex]
@@ -61,7 +62,7 @@ const pushTablesToApi = async(state: AnnotatorState, previousState: AnnotatorSta
6162
const rateTokenCopy = rateToken
6263
await new Promise(r => setTimeout(r, 150));
6364
if (rateToken === rateTokenCopy) {
64-
const response = await axios.post(`${APIAddress}/${project}/${dataDir}/tables/${image.name}`, tables)
65+
const response = await axios.post(`${APIAddress}/${bucket}/${project}/${dataDir}/tables/${image.name}`, tables)
6566
if (response.status === 200) {
6667
state.setIsInSync(true)
6768
}

src/BrowsingMenu.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Box from "@mui/material/Box";
2+
import BackButton from "./components/MenuComponents/BackButton";
3+
import React from "react";
4+
5+
type BrowsingMenuProps = {
6+
back_to_level: number
7+
}
8+
const BrowsingMenu = ({back_to_level}: BrowsingMenuProps) => {
9+
return (
10+
<Box sx={{
11+
display: "flex", width: "100%", position: "fixed", "top": 0, "left": 0,
12+
zIndex: 99, background: "lightgrey"
13+
}}>
14+
<BackButton to_level={back_to_level}/>
15+
</Box>
16+
)
17+
}
18+
19+
export default BrowsingMenu

src/BucketPage.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import create from "zustand";
2+
import React, {useEffect} from "react";
3+
import {APIAddress} from "./api";
4+
import Table from '@mui/material/Table';
5+
import TableBody from '@mui/material/TableBody';
6+
import TableCell from '@mui/material/TableCell';
7+
import TableContainer from '@mui/material/TableContainer';
8+
import TableHead from '@mui/material/TableHead';
9+
import TableRow from '@mui/material/TableRow';
10+
import Paper from '@mui/material/Paper';
11+
import Link from '@mui/material/Link';
12+
import Container from '@mui/material/Container';
13+
import CircularProgress from '@mui/material/CircularProgress';
14+
import Box from '@mui/material/Box';
15+
import Typography from '@mui/material/Typography';
16+
import {getProjectBucket} from "./path";
17+
import BrowsingMenu from "./BrowsingMenu";
18+
19+
export type BucketPageState = {
20+
projectNames?: string[]
21+
fetchAllProjectNames: () => void
22+
}
23+
24+
export const useStore = create<BucketPageState>((set, get) => ({
25+
projectNames: undefined,
26+
fetchAllProjectNames: async() => {
27+
const projectBucket = getProjectBucket()
28+
const response = await fetch(`${APIAddress}/${projectBucket}`)
29+
const projectNames: string[] = (await response.json())["projectNames"]
30+
set({projectNames})
31+
}
32+
}))
33+
34+
function BucketPage() {
35+
const bucketName = getProjectBucket()
36+
const projectNames= useStore(state => state.projectNames)
37+
const fetchAllProjectNames = useStore(state => state.fetchAllProjectNames)
38+
39+
useEffect(() => {
40+
if(projectNames === undefined) {
41+
fetchAllProjectNames()
42+
}
43+
})
44+
if (projectNames !== undefined) {
45+
return (
46+
<Container maxWidth="sm" style={{"textAlign": "center"}}>
47+
<BrowsingMenu back_to_level={0}/>
48+
<Paper>
49+
<Typography variant="h5" gutterBottom>
50+
{bucketName}
51+
</Typography>
52+
</Paper>
53+
<TableContainer component={Paper}>
54+
<Table sx={{ minWidth: 350, maxWidth: 650}} aria-label="packages">
55+
<TableHead>
56+
<TableRow>
57+
<TableCell>Projekt</TableCell>
58+
</TableRow>
59+
</TableHead>
60+
<TableBody>
61+
{projectNames.map((name, i) => (
62+
<TableRow
63+
key={i}
64+
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
65+
>
66+
<TableCell component="th" scope="row">
67+
<Link href={`${bucketName}/${name}`}
68+
underline="hover">
69+
{name}
70+
</Link>
71+
</TableCell>
72+
</TableRow>
73+
))}
74+
</TableBody>
75+
</Table>
76+
</TableContainer>
77+
</Container>
78+
)
79+
} else {
80+
return <Box sx={{ display: 'flex' }}>
81+
<CircularProgress />
82+
</Box>
83+
}
84+
85+
}
86+
87+
export default BucketPage

0 commit comments

Comments
 (0)