ganteng88 commited on
Commit
5514904
Β·
1 Parent(s): c654d2a

Upload 7 files

Browse files
Files changed (7) hide show
  1. README.md +6 -5
  2. app.py +265 -0
  3. ham1.ckpt +3 -0
  4. index.html +50 -0
  5. requirements.txt +63 -0
  6. resnet18.py +129 -0
  7. style.css +83 -0
README.md CHANGED
@@ -1,12 +1,13 @@
1
  ---
2
- title: Ham1000
3
- emoji: πŸ“Š
4
- colorFrom: blue
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 3.50.2
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Bodypartxr
3
+ emoji: πŸ†
4
+ colorFrom: red
5
+ colorTo: pink
6
  sdk: gradio
7
+ sdk_version: 3.47.1
8
  app_file: app.py
9
  pinned: false
10
+ license: unknown
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,265 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ from torchvision.transforms import transforms
4
+ import numpy as np
5
+ from typing import Optional
6
+ import torch.nn as nn
7
+ import os
8
+ from utils import page_utils
9
+
10
+ class BasicBlock(nn.Module):
11
+ """ResNet Basic Block.
12
+
13
+ Parameters
14
+ ----------
15
+ in_channels : int
16
+ Number of input channels
17
+ out_channels : int
18
+ Number of output channels
19
+ stride : int, optional
20
+ Convolution stride size, by default 1
21
+ identity_downsample : Optional[torch.nn.Module], optional
22
+ Downsampling layer, by default None
23
+ """
24
+
25
+ def __init__(self,
26
+ in_channels: int,
27
+ out_channels: int,
28
+ stride: int = 1,
29
+ identity_downsample: Optional[torch.nn.Module] = None):
30
+ super(BasicBlock, self).__init__()
31
+ self.conv1 = nn.Conv2d(in_channels,
32
+ out_channels,
33
+ kernel_size = 3,
34
+ stride = stride,
35
+ padding = 1)
36
+ self.bn1 = nn.BatchNorm2d(out_channels)
37
+ self.relu = nn.ReLU()
38
+ self.conv2 = nn.Conv2d(out_channels,
39
+ out_channels,
40
+ kernel_size = 3,
41
+ stride = 1,
42
+ padding = 1)
43
+ self.bn2 = nn.BatchNorm2d(out_channels)
44
+ self.identity_downsample = identity_downsample
45
+
46
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
47
+ """Apply forward computation."""
48
+ identity = x
49
+ x = self.conv1(x)
50
+ x = self.bn1(x)
51
+ x = self.relu(x)
52
+ x = self.conv2(x)
53
+ x = self.bn2(x)
54
+
55
+ # Apply an operation to the identity output.
56
+ # Useful to reduce the layer size and match from conv2 output
57
+ if self.identity_downsample is not None:
58
+ identity = self.identity_downsample(identity)
59
+ x += identity
60
+ x = self.relu(x)
61
+ return x
62
+
63
+ class ResNet18(nn.Module):
64
+ """Construct ResNet-18 Model.
65
+
66
+ Parameters
67
+ ----------
68
+ input_channels : int
69
+ Number of input channels
70
+ num_classes : int
71
+ Number of class outputs
72
+ """
73
+
74
+ def __init__(self, input_channels, num_classes):
75
+
76
+ super(ResNet18, self).__init__()
77
+ self.conv1 = nn.Conv2d(input_channels,
78
+ 64, kernel_size = 7,
79
+ stride = 2, padding=3)
80
+ self.bn1 = nn.BatchNorm2d(64)
81
+ self.relu = nn.ReLU()
82
+ self.maxpool = nn.MaxPool2d(kernel_size = 3,
83
+ stride = 2,
84
+ padding = 1)
85
+
86
+ self.layer1 = self._make_layer(64, 64, stride = 1)
87
+ self.layer2 = self._make_layer(64, 128, stride = 2)
88
+ self.layer3 = self._make_layer(128, 256, stride = 2)
89
+ self.layer4 = self._make_layer(256, 512, stride = 2)
90
+
91
+ # Last layers
92
+ self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
93
+ self.fc = nn.Linear(512, num_classes)
94
+
95
+ def identity_downsample(self, in_channels: int, out_channels: int) -> nn.Module:
96
+ """Downsampling block to reduce the feature sizes."""
97
+ return nn.Sequential(
98
+ nn.Conv2d(in_channels,
99
+ out_channels,
100
+ kernel_size = 3,
101
+ stride = 2,
102
+ padding = 1),
103
+ nn.BatchNorm2d(out_channels)
104
+ )
105
+
106
+ def _make_layer(self, in_channels: int, out_channels: int, stride: int) -> nn.Module:
107
+ """Create sequential basic block."""
108
+ identity_downsample = None
109
+
110
+ # Add downsampling function
111
+ if stride != 1:
112
+ identity_downsample = self.identity_downsample(in_channels, out_channels)
113
+
114
+ return nn.Sequential(
115
+ BasicBlock(in_channels, out_channels, identity_downsample=identity_downsample, stride=stride),
116
+ BasicBlock(out_channels, out_channels)
117
+ )
118
+
119
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
120
+ x = self.conv1(x)
121
+ x = self.bn1(x)
122
+ x = self.relu(x)
123
+ x = self.maxpool(x)
124
+
125
+ x = self.layer1(x)
126
+ x = self.layer2(x)
127
+ x = self.layer3(x)
128
+ x = self.layer4(x)
129
+
130
+ x = self.avgpool(x)
131
+ x = x.view(x.shape[0], -1)
132
+ x = self.fc(x)
133
+ return x
134
+
135
+ model = ResNet18(1, 7)
136
+
137
+ checkpoint = torch.load('ham1.ckpt', map_location=torch.device('cpu'))
138
+
139
+ # The state dict will contains net.layer_name
140
+ # Our model doesn't contains `net.` so we have to rename it
141
+ state_dict = checkpoint['state_dict']
142
+ for key in list(state_dict.keys()):
143
+ if 'net.' in key:
144
+ state_dict[key.replace('net.', '')] = state_dict[key]
145
+ del state_dict[key]
146
+
147
+ model.load_state_dict(state_dict)
148
+ model.eval()
149
+
150
+ class_names = ['akk', 'bcc', 'bkl', 'df', 'mel','nv','vasc']
151
+ class_names.sort()
152
+
153
+ examples_dir = "sample"
154
+
155
+ transformation_pipeline = transforms.Compose([
156
+ transforms.ToPILImage(),
157
+ transforms.Grayscale(num_output_channels=1),
158
+ transforms.CenterCrop((224, 224)),
159
+ transforms.ToTensor(),
160
+ transforms.Normalize(mean=[0.485], std=[0.229])
161
+ ])
162
+
163
+
164
+ def preprocess_image(image: np.ndarray):
165
+ """Preprocess the input image.
166
+
167
+ Note that the input image is in RGB mode.
168
+
169
+ Parameters
170
+ ----------
171
+ image: np.ndarray
172
+ Input image from callback.
173
+ """
174
+
175
+ image = transformation_pipeline(image)
176
+ image = torch.unsqueeze(image, 0)
177
+
178
+ return image
179
+
180
+
181
+ def image_classifier(inp):
182
+ """Image Classifier Function.
183
+
184
+ Parameters
185
+ ----------
186
+ inp: Optional[np.ndarray] = None
187
+ Input image from callback
188
+
189
+ Returns
190
+ -------
191
+ Dict
192
+ A dictionary class names and its probability
193
+ """
194
+
195
+ # If input not valid, return dummy data or raise error
196
+ if inp is None:
197
+ return {'cat': 0.3, 'dog': 0.7}
198
+
199
+ # preprocess
200
+ image = preprocess_image(inp)
201
+ image = image.to(dtype=torch.float32)
202
+
203
+ # inference
204
+ result = model(image)
205
+
206
+ # postprocess
207
+ result = torch.nn.functional.softmax(result, dim=1) # apply softmax
208
+ result = result[0].detach().numpy().tolist() # take the first batch
209
+ labeled_result = {name:score for name, score in zip(class_names, result)}
210
+
211
+ return labeled_result
212
+
213
+ # gradio code block for input and output
214
+ with gr.Blocks() as app:
215
+ gr.Markdown("# Skin Cancer Classification")
216
+
217
+ with open('index.html', encoding="utf-8") as f:
218
+ description = f.read()
219
+
220
+ # gradio code block for input and output
221
+ with gr.Blocks(theme=gr.themes.Default(primary_hue=page_utils.KALBE_THEME_COLOR, secondary_hue=page_utils.KALBE_THEME_COLOR).set(
222
+ button_primary_background_fill="*primary_600",
223
+ button_primary_background_fill_hover="*primary_500",
224
+ button_primary_text_color="white",
225
+ )) as app:
226
+ with gr.Column():
227
+ gr.HTML(description)
228
+
229
+ with gr.Row():
230
+ with gr.Column():
231
+ inp_img = gr.Image()
232
+ with gr.Row():
233
+ clear_btn = gr.Button(value="Clear")
234
+ process_btn = gr.Button(value="Process", variant="primary")
235
+ with gr.Column():
236
+ out_txt = gr.Label(label="Probabilities", num_top_classes=3)
237
+
238
+ process_btn.click(image_classifier, inputs=inp_img, outputs=out_txt)
239
+ clear_btn.click(lambda:(
240
+ gr.update(value=None),
241
+ gr.update(value=None)
242
+ ),
243
+ inputs=None,
244
+ outputs=[inp_img, out_txt])
245
+
246
+ gr.Markdown("## Image Examples")
247
+ gr.Examples(
248
+ examples=[os.path.join(examples_dir, "ISIC_0000108_downsampled.jpeg"),
249
+ os.path.join(examples_dir, "ISIC_0000142_downsampled.jpeg"),
250
+ os.path.join(examples_dir, "ISIC_0012792_downsampled.jpeg"),
251
+ os.path.join(examples_dir, "ISIC_0024452.jpeg"),
252
+ os.path.join(examples_dir, "ISIC_0025957.jpeg"),
253
+ os.path.join(examples_dir, "ISIC_0026876.jpeg"),
254
+ os.path.join(examples_dir, "ISIC_0027385.jpeg"),
255
+ os.path.join(examples_dir, "ISIC_0030956.jpeg"),
256
+ ],
257
+ inputs=inp_img,
258
+ outputs=out_txt,
259
+ fn=image_classifier,
260
+ cache_examples=False,
261
+ )
262
+ gr.Markdown(line_breaks=True, value='Author: Jason Adrian ([email protected]) <div class="row"><a href="https://github.com/jasonadriann?tab=repositories"><img alt="GitHub" src="https://img.shields.io/badge/Jason%20Adrian-000000?logo=github"> </div>')
263
+
264
+ # demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label")
265
+ app.launch(share=True)
ham1.ckpt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4b07ac05dfb7cb1b0f0d57ad5baf923acd0d4da5352588ae492f4faa970e2833
3
+ size 150928119
index.html ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <link rel="stylesheet" href="file/style.css" />
5
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
6
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
7
+ <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap" rel="stylesheet" />
8
+ <title><strong>Body Part Classification</strong></title>
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <h1 class="title"><strong> Body Part Classification</strong></h1>
13
+ <h2 class="subtitle"><strong>Kalbe Digital Lab</strong></h2>
14
+ <section class="overview">
15
+ <div class="grid-container">
16
+ <h3 class="overview-heading"><span class="vl">Overview</span></h3>
17
+ <p class="overview-content">
18
+ The Body Part Classification program serves the critical purpose of categorizing body parts from DICOM x-ray scans into five distinct classes: abdominal, adult chest, pediatric chest, spine, and others. This program trained using ResNet18 model.
19
+ </p>
20
+ </div>
21
+ <div class="grid-container">
22
+ <h3 class="overview-heading"><span class="vl">Dataset</span></h3>
23
+ <div>
24
+ <p class="overview-content">
25
+ The program has been meticulously trained on a robust and diverse dataset, specifically <a href="https://vindr.ai/datasets/bodypartxr" target="_blank">VinDrBodyPartXR Dataset.</a>.
26
+ <br/>
27
+ This dataset is introduced by Vingroup of Big Data Institute which include 16,093 x-ray images that are collected and manually annotated. It is a highly valuable resource that has been instrumental in the training of our model.
28
+ </p>
29
+ <ul>
30
+ <li>Objective: Body Part Identification</li>
31
+ <li>Task: Classification</li>
32
+ <li>Modality: Grayscale Images</li>
33
+ </ul>
34
+ </div>
35
+ </div>
36
+ <div class="grid-container">
37
+ <h3 class="overview-heading"><span class="vl">Model Architecture</span></h3>
38
+ <div>
39
+ <p class="overview-content">
40
+ The model architecture of ResNet18 to train x-ray images for classifying body part.
41
+ </p>
42
+ <img class="content-image" src="file/figures/ResNet-18.png" alt="model-architecture" width="425" height="115" style="vertical-align:middle" />
43
+ </div>
44
+ </div>
45
+ </section>
46
+ <h3 class="overview-heading"><span class="vl">Demo</span></h3>
47
+ <p class="overview-content">Please select or upload a body part x-ray scan image to see the capabilities of body part classification with this model</p>
48
+ </div>
49
+ </body>
50
+ </html>
requirements.txt ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==23.2.1
2
+ altair==5.1.2
3
+ annotated-types==0.6.0
4
+ anyio==3.7.1
5
+ attrs==23.1.0
6
+ certifi==2023.7.22
7
+ charset-normalizer==3.3.0
8
+ click==8.1.7
9
+ colorama==0.4.6
10
+ contourpy==1.1.1
11
+ cycler==0.12.1
12
+ exceptiongroup==1.1.3
13
+ fastapi==0.103.2
14
+ ffmpy==0.3.1
15
+ filelock==3.12.4
16
+ fonttools==4.43.1
17
+ fsspec==2023.9.2
18
+ gradio==3.47.1
19
+ gradio_client==0.6.0
20
+ h11==0.14.0
21
+ httpcore==0.18.0
22
+ httpx==0.25.0
23
+ huggingface-hub==0.17.3
24
+ idna==3.4
25
+ importlib-resources==6.1.0
26
+ Jinja2==3.1.2
27
+ jsonschema==4.19.1
28
+ jsonschema-specifications==2023.7.1
29
+ kiwisolver==1.4.5
30
+ MarkupSafe==2.1.3
31
+ matplotlib==3.8.0
32
+ mpmath==1.3.0
33
+ networkx==3.1
34
+ numpy==1.26.0
35
+ orjson==3.9.7
36
+ packaging==23.2
37
+ pandas==2.1.1
38
+ Pillow==10.0.1
39
+ pydantic==2.4.2
40
+ pydantic_core==2.10.1
41
+ pydub==0.25.1
42
+ pyparsing==3.1.1
43
+ python-dateutil==2.8.2
44
+ python-multipart==0.0.6
45
+ pytz==2023.3.post1
46
+ PyYAML==6.0.1
47
+ referencing==0.30.2
48
+ requests==2.31.0
49
+ rpds-py==0.10.4
50
+ semantic-version==2.10.0
51
+ six==1.16.0
52
+ sniffio==1.3.0
53
+ starlette==0.27.0
54
+ sympy==1.12
55
+ toolz==0.12.0
56
+ torch==2.1.0
57
+ torchvision==0.16.0
58
+ tqdm==4.66.1
59
+ typing_extensions==4.8.0
60
+ tzdata==2023.3
61
+ urllib3==2.0.6
62
+ uvicorn==0.23.2
63
+ websockets==11.0.3
resnet18.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+
3
+ import torch.nn as nn
4
+ import torch
5
+
6
+ class BasicBlock(nn.Module):
7
+ """ResNet Basic Block.
8
+
9
+ Parameters
10
+ ----------
11
+ in_channels : int
12
+ Number of input channels
13
+ out_channels : int
14
+ Number of output channels
15
+ stride : int, optional
16
+ Convolution stride size, by default 1
17
+ identity_downsample : Optional[torch.nn.Module], optional
18
+ Downsampling layer, by default None
19
+ """
20
+
21
+ def __init__(self,
22
+ in_channels: int,
23
+ out_channels: int,
24
+ stride: int = 1,
25
+ identity_downsample: Optional[torch.nn.Module] = None):
26
+ super(BasicBlock, self).__init__()
27
+ self.conv1 = nn.Conv2d(in_channels,
28
+ out_channels,
29
+ kernel_size = 3,
30
+ stride = stride,
31
+ padding = 1)
32
+ self.bn1 = nn.BatchNorm2d(out_channels)
33
+ self.relu = nn.ReLU()
34
+ self.conv2 = nn.Conv2d(out_channels,
35
+ out_channels,
36
+ kernel_size = 3,
37
+ stride = 1,
38
+ padding = 1)
39
+ self.bn2 = nn.BatchNorm2d(out_channels)
40
+ self.identity_downsample = identity_downsample
41
+
42
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
43
+ """Apply forward computation."""
44
+ identity = x
45
+ x = self.conv1(x)
46
+ x = self.bn1(x)
47
+ x = self.relu(x)
48
+ x = self.conv2(x)
49
+ x = self.bn2(x)
50
+
51
+ # Apply an operation to the identity output.
52
+ # Useful to reduce the layer size and match from conv2 output
53
+ if self.identity_downsample is not None:
54
+ identity = self.identity_downsample(identity)
55
+ x += identity
56
+ x = self.relu(x)
57
+ return x
58
+
59
+ class ResNet18(nn.Module):
60
+ """Construct ResNet-18 Model.
61
+
62
+ Parameters
63
+ ----------
64
+ input_channels : int
65
+ Number of input channels
66
+ num_classes : int
67
+ Number of class outputs
68
+ """
69
+
70
+ def __init__(self, input_channels, num_classes):
71
+
72
+ super(ResNet18, self).__init__()
73
+ self.conv1 = nn.Conv2d(input_channels,
74
+ 64, kernel_size = 7,
75
+ stride = 2, padding=3)
76
+ self.bn1 = nn.BatchNorm2d(64)
77
+ self.relu = nn.ReLU()
78
+ self.maxpool = nn.MaxPool2d(kernel_size = 3,
79
+ stride = 2,
80
+ padding = 1)
81
+
82
+ self.layer1 = self._make_layer(64, 64, stride = 1)
83
+ self.layer2 = self._make_layer(64, 128, stride = 2)
84
+ self.layer3 = self._make_layer(128, 256, stride = 2)
85
+ self.layer4 = self._make_layer(256, 512, stride = 2)
86
+
87
+ # Last layers
88
+ self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
89
+ self.fc = nn.Linear(512, num_classes)
90
+
91
+ def identity_downsample(self, in_channels: int, out_channels: int) -> nn.Module:
92
+ """Downsampling block to reduce the feature sizes."""
93
+ return nn.Sequential(
94
+ nn.Conv2d(in_channels,
95
+ out_channels,
96
+ kernel_size = 3,
97
+ stride = 2,
98
+ padding = 1),
99
+ nn.BatchNorm2d(out_channels)
100
+ )
101
+
102
+ def _make_layer(self, in_channels: int, out_channels: int, stride: int) -> nn.Module:
103
+ """Create sequential basic block."""
104
+ identity_downsample = None
105
+
106
+ # Add downsampling function
107
+ if stride != 1:
108
+ identity_downsample = self.identity_downsample(in_channels, out_channels)
109
+
110
+ return nn.Sequential(
111
+ BasicBlock(in_channels, out_channels, identity_downsample=identity_downsample, stride=stride),
112
+ BasicBlock(out_channels, out_channels)
113
+ )
114
+
115
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
116
+ x = self.conv1(x)
117
+ x = self.bn1(x)
118
+ x = self.relu(x)
119
+ x = self.maxpool(x)
120
+
121
+ x = self.layer1(x)
122
+ x = self.layer2(x)
123
+ x = self.layer3(x)
124
+ x = self.layer4(x)
125
+
126
+ x = self.avgpool(x)
127
+ x = x.view(x.shape[0], -1)
128
+ x = self.fc(x)
129
+ return x
style.css ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ body {
6
+ font-family: 'Source Sans Pro', sans-serif;
7
+ font-size: 16px;
8
+ }
9
+
10
+ .container {
11
+ width: 100%;
12
+ margin: 0 auto;
13
+ }
14
+
15
+ .title {
16
+ font-size: 24px !important;
17
+ font-weight: 600 !important;
18
+ letter-spacing: 0em;
19
+ text-align: center;
20
+ color: #374159 !important;
21
+ }
22
+
23
+ .subtitle {
24
+ font-size: 24px !important;
25
+ font-style: italic;
26
+ font-weight: 400 !important;
27
+ letter-spacing: 0em;
28
+ text-align: center;
29
+ color: #1d652a !important;
30
+ padding-bottom: 0.5em;
31
+ }
32
+
33
+ .overview-heading {
34
+ font-size: 24px !important;
35
+ font-weight: 600 !important;
36
+ letter-spacing: 0em;
37
+ text-align: left;
38
+ }
39
+
40
+ .overview-content {
41
+ font-size: 14px !important;
42
+ font-weight: 400 !important;
43
+ line-height: 30px !important;
44
+ letter-spacing: 0em;
45
+ text-align: left;
46
+ }
47
+
48
+ .content-image {
49
+ width: 100% !important;
50
+ height: auto !important;
51
+ }
52
+
53
+ .vl {
54
+ border-left: 5px solid #1d652a;
55
+ padding-left: 20px;
56
+ color: #1d652a !important;
57
+ }
58
+
59
+ .grid-container {
60
+ display: grid;
61
+ grid-template-columns: 1fr 2fr;
62
+ gap: 20px;
63
+ align-items: flex-start;
64
+ margin-bottom: 0.7em;
65
+ }
66
+
67
+ .grid-container:nth-child(2) {
68
+ align-items: center;
69
+ }
70
+
71
+ @media screen and (max-width: 768px) {
72
+ .container {
73
+ width: 90%;
74
+ }
75
+
76
+ .grid-container {
77
+ display: block;
78
+ }
79
+
80
+ .overview-heading {
81
+ font-size: 18px !important;
82
+ }
83
+ }